From 4575812a3b912269128e48cd8ac446b8ef750cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 19 Nov 2019 09:32:33 +0100 Subject: [PATCH 01/43] Initialize a CRUD for entities --- CRUD/Controller/CRUDController.php | 128 ++++++++++++++++++++ CRUD/Resolver/Resolver.php | 110 +++++++++++++++++ CRUD/Routing/CRUDRoutesLoader.php | 87 +++++++++++++ CRUD/Templating/TwigCRUDResolver.php | 61 ++++++++++ Controller/AdminCountryCRUDController.php | 43 +++++++ DependencyInjection/ChillMainExtension.php | 28 +++++ DependencyInjection/Configuration.php | 17 +++ Resources/config/crud/country.yml | 4 + Resources/config/services/crud.yml | 15 +++ Resources/views/CRUD/_inc/default.html.twig | 1 + Resources/views/CRUD/index.html.twig | 39 ++++++ 11 files changed, 533 insertions(+) create mode 100644 CRUD/Controller/CRUDController.php create mode 100644 CRUD/Resolver/Resolver.php create mode 100644 CRUD/Routing/CRUDRoutesLoader.php create mode 100644 CRUD/Templating/TwigCRUDResolver.php create mode 100644 Controller/AdminCountryCRUDController.php create mode 100644 Resources/config/crud/country.yml create mode 100644 Resources/config/services/crud.yml create mode 100644 Resources/views/CRUD/_inc/default.html.twig create mode 100644 Resources/views/CRUD/index.html.twig diff --git a/CRUD/Controller/CRUDController.php b/CRUD/Controller/CRUDController.php new file mode 100644 index 000000000..9153833e8 --- /dev/null +++ b/CRUD/Controller/CRUDController.php @@ -0,0 +1,128 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Pagination\PaginatorFactory; + +/** + * + * + */ +abstract class CRUDController extends Controller +{ + /** + * + * @var PaginatorFactory + */ + protected $paginatorFactory; + + abstract protected function getEntity(): string; + + abstract protected function orderingOptions(): array; + + protected function getDefaultOrdering(): array + { + return $this->orderingOptions(); + } + + protected function getTemplate($action): string + { + switch($action) { + case 'index': + return '@ChillMain\CRUD\index.html.twig'; + default: + throw new LogicException("action not supported: $action"); + } + } + + protected function getTemplateParameters($action): array + { + return []; + } + + protected function processTemplateParameters($action): array + { + $configured = $this->getTemplateParameters($action); + + switch($action) { + case 'index': + $default = [ + 'columns' => $this->getDoctrine()->getManager() + ->getClassMetadata($this->getEntity()) + ->getIdentifierFieldNames(), + 'actions' => ['edit', 'delete'] + ]; + break; + default: + throw new \LogicException("this action is not supported: $action"); + } + + $result = \array_merge($default, $configured); + + // add constants + $result['class'] = $this->getEntity(); + + return $result; + } + + protected function orderQuery(QueryBuilder $query, Request $request): QueryBuilder + { + $defaultOrdering = $this->getDefaultOrdering(); + + foreach ($defaultOrdering as $sort => $order) { + $query->addOrderBy('e.'.$sort, $order); + } + + return $query; + } + + public function index(Request $request) + { + $totalItems = $this->getDoctrine()->getManager() + ->createQuery("SELECT COUNT(e) FROM ".$this->getEntity()." e") + ->getSingleScalarResult() + ; + + $query = $this->getDoctrine()->getManager() + ->createQueryBuilder() + ->select('e') + ->from($this->getEntity(), 'e'); + + $this->orderQuery($query, $request); + + $paginator = $this->paginatorFactory->create($totalItems); + + $query->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + ->setMaxResults($paginator->getItemsPerPage()) + ; + + $entities = $query->getQuery()->getResult(); + + return $this->render($this->getTemplate('index'), \array_merge([ + 'entities' => $entities, + ], $this->processTemplateParameters('index')) + ); + } +} diff --git a/CRUD/Resolver/Resolver.php b/CRUD/Resolver/Resolver.php new file mode 100644 index 000000000..0eac94d01 --- /dev/null +++ b/CRUD/Resolver/Resolver.php @@ -0,0 +1,110 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Resolver; + +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; + +/** + * + * + */ +class Resolver +{ + /** + * + * @var EntityManagerInterface + */ + protected $em; + + /** + * + * @var \Symfony\Component\PropertyAccess\PropertyAccessor + */ + protected $propertyAccess; + + function __construct(EntityManagerInterface $em) + { + $this->em = $em; + + $this->buildPropertyAccess(); + } + + + private function buildPropertyAccess() + { + $this->propertyAccess = PropertyAccess::createPropertyAccessorBuilder() + ->enableExceptionOnInvalidIndex() + ->getPropertyAccessor(); + } + + /** + * Return the data at given path. + * + * Path are given to + * + * @param object $entity + * @param string $path + */ + public function getData($entity, $path) + { + return $this->propertyAccess->getValue($entity, $path); + } + + 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: + return '@ChillMain/CRUD/_inc/default.html.twig'; + } + } + + /** + * Get the object on which the path apply + * + * This methods recursively parse the path and entity and return the entity + * which will deliver the info, and the last path. + * + * @param object $entity + * @param string $path + * @return array [$focusedEntity, $lastPath] + */ + private function getFocusedEntity($entity, $path) + { + if (\strpos($path, '.') === FALSE) { + return [$entity, $path]; + } + + $exploded = \explode('.', $path); + + $subEntity = $this->propertyAccess + ->getValue($entity, $exploded[0]); + + return $this->getFocusedEntity($subEntity, + \implode('.', \array_slice($exploded, 1))); + } +} diff --git a/CRUD/Routing/CRUDRoutesLoader.php b/CRUD/Routing/CRUDRoutesLoader.php new file mode 100644 index 000000000..669401ce9 --- /dev/null +++ b/CRUD/Routing/CRUDRoutesLoader.php @@ -0,0 +1,87 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Routing; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + + +/** + * + * + */ +class CRUDRoutesLoader +{ + protected $config = []; + + public function __construct($config) + { + $this->config = $config; + } + + protected function addDummyConfig() + { + $this->config[] = [ + 'name' => 'country', + 'actions' => ['index'],//, 'new', 'edit', 'delete'], + 'base_path' => '/admin/country', + 'controller' => \Chill\MainBundle\Controller\AdminCountryCRUDController::class + ]; + } + + + public function load() + { + $collection = new RouteCollection(); + + foreach ($this->config as $config) { + $collection->addCollection($this->loadConfig($config)); + } + + return $collection; + } + + protected function loadConfig($config): RouteCollection + { + $collection = new RouteCollection(); + + foreach ($config['actions'] as $action) { + $defaults = [ + '_controller' => $config['controller'].'::'.$action + ]; + + if ($action === 'index') { + $path = "{_locale}".$config['base_path']; + $route = new Route($path, $defaults); + } else { + $path = "{_locale}".$config['base_path'].'/{id}/'.$action; + $requirements = [ + '{id}' => '\d+' + ]; + $route = new Route($path, $defaults, $requirements); + } + + $collection->add('chill_crud_'.$config['name'].'_'.$action, $route); + } + + return $collection; + } +} diff --git a/CRUD/Templating/TwigCRUDResolver.php b/CRUD/Templating/TwigCRUDResolver.php new file mode 100644 index 000000000..ca398814c --- /dev/null +++ b/CRUD/Templating/TwigCRUDResolver.php @@ -0,0 +1,61 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Templating; + +use Chill\MainBundle\CRUD\Resolver\Resolver; +use Twig\TwigFilter; +use Twig\Extension\AbstractExtension; +use Twig\Environment; + +/** + * Twig filters to display data in crud template + * + */ +class TwigCRUDResolver extends AbstractExtension +{ + /** + * + * @var Resolver + */ + protected $resolver; + + function __construct(Resolver $resolver) + { + $this->resolver = $resolver; + } + + public function getFilters() + { + return [ + new TwigFilter('chill_crud_display', [$this, 'display'], + ['needs_environment' => true, 'is_safe' => ['html']]) + ]; + } + + public function display(Environment $env, $entity, $path): string + { + $data = $this->resolver->getData($entity, $path); + $template = $this->resolver->getTwigTemplate($entity, $path); + + return $env->render($template, ['data' => $data, 'entity' => $entity, ]); + } + +} diff --git a/Controller/AdminCountryCRUDController.php b/Controller/AdminCountryCRUDController.php new file mode 100644 index 000000000..00ce5c9d8 --- /dev/null +++ b/Controller/AdminCountryCRUDController.php @@ -0,0 +1,43 @@ +paginatorFactory = $paginator; + } + + protected function getEntity(): string + { + return Country::class; + } + + protected function orderingOptions(): array + { + return [ + 'countryCode' => 'ASC' + ]; + } + + protected function getTemplateParameters($action): array + { + switch ($action) { + case 'index': + return [ + 'columns' => [ 'countryCode', 'name' ], + 'title' => 'Liste des pays' + ]; + } + } +} diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php index 7b4b0243a..6e7c22581 100644 --- a/DependencyInjection/ChillMainExtension.php +++ b/DependencyInjection/ChillMainExtension.php @@ -32,6 +32,8 @@ use Chill\MainBundle\Doctrine\DQL\JsonAggregate; use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray; use Chill\MainBundle\Doctrine\DQL\Similarity; use Chill\MainBundle\Doctrine\DQL\OverlapsI; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; /** * This class load config for chillMainExtension. @@ -96,6 +98,8 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, array('homepage' => $config['widgets']['homepage']): array() ); + + $this->configureCruds($container, $config['cruds']); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); @@ -117,6 +121,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $loader->load('services/phonenumber.yml'); $loader->load('services/cache.yml'); $loader->load('services/templating.yml'); + $loader->load('services/crud.yml'); } public function getConfiguration(array $config, ContainerBuilder $container) @@ -194,4 +199,27 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, 'channels' => array('chill') )); } + + /** + * + * @param ContainerBuilder $container + * @param array $config the config under 'cruds' key + * @return null + */ + protected function configureCruds(ContainerBuilder $container, $config) + { + if (count($config) === 0) { + return; + } + + $container->setParameter('chill_main_crud_route_loader_config', $config); + + $definition = new Definition(); + $definition + ->setClass(\Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader::class) + ->addArgument('%chill_main_crud_route_loader_config%') + ; + + $container->setDefinition('chill_main_crud_route_loader', $definition); + } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 7909bbd01..c901fca40 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -116,6 +116,23 @@ class Configuration implements ConfigurationInterface ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder)) ->end() // end of widgets/children ->end() // end of widgets + ->arrayNode('cruds') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('controller')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->arrayNode('actions') + ->scalarPrototype()->end() + //->defaultValue(['index', 'new', 'edit', 'show', 'delete']) + //->ifEmpty()->thenInvalid() + ->end() + ->end() + ->end() + + ->end() ->end() // end of root/children ->end() // end of root ; diff --git a/Resources/config/crud/country.yml b/Resources/config/crud/country.yml new file mode 100644 index 000000000..751529b12 --- /dev/null +++ b/Resources/config/crud/country.yml @@ -0,0 +1,4 @@ +name: 'country' +actions: ['index', 'new', 'edit', 'delete'] +base_path: '/admin/country' +controller: 'Chill\MainBundle\CRUD\Controller\CRUDController' \ No newline at end of file diff --git a/Resources/config/services/crud.yml b/Resources/config/services/crud.yml new file mode 100644 index 000000000..ab686d34e --- /dev/null +++ b/Resources/config/services/crud.yml @@ -0,0 +1,15 @@ +services: +# Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader: +# +# tags: +# - routing.loader + + Chill\MainBundle\CRUD\Resolver\Resolver: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + + Chill\MainBundle\CRUD\Templating\TwigCRUDResolver: + arguments: + $resolver: '@Chill\MainBundle\CRUD\Resolver\Resolver' + tags: + - { name: twig.extension } \ No newline at end of file diff --git a/Resources/views/CRUD/_inc/default.html.twig b/Resources/views/CRUD/_inc/default.html.twig new file mode 100644 index 000000000..0e9d6dd4c --- /dev/null +++ b/Resources/views/CRUD/_inc/default.html.twig @@ -0,0 +1 @@ +{{ data }} \ No newline at end of file diff --git a/Resources/views/CRUD/index.html.twig b/Resources/views/CRUD/index.html.twig new file mode 100644 index 000000000..83c8683c4 --- /dev/null +++ b/Resources/views/CRUD/index.html.twig @@ -0,0 +1,39 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block content %} +

{{ title|default('List of %class%')|trans({'%class%': class}) }}

+ + {% if entities|length == 0 %} +

{{ no_existing_entities_sentences|default('No entities')|trans }}

+ {% else %} + + + + {% for c in columns %} + + {% endfor %} + + + + + {% for entity in entities %} + + {% for c in columns %} + + {% endfor %} + + + {% endfor %} + +
{{ c|trans }} 
{{ entity|chill_crud_display(c) }} +
    + {% for action in actions %} +
  • {{ action }}
  • + {% endfor %} +
+
+ + {% endif %} + + +{% endblock content %} \ No newline at end of file From e6bf77530b7cbab241cd323886ee0ccd28ef40ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 22 Nov 2019 13:45:30 +0100 Subject: [PATCH 02/43] work on basic CRUDController and improve configuration --- CRUD/Controller/CRUDController.php | 310 +++++++++++++++++-- CRUD/Routing/CRUDRoutesLoader.php | 2 +- Controller/AdminCountryCRUDController.php | 23 -- DependencyInjection/ChillMainExtension.php | 23 ++ DependencyInjection/Configuration.php | 5 +- Resources/views/CRUD/_edit_content.html.twig | 28 ++ Resources/views/CRUD/_edit_title.html.twig | 1 + Resources/views/CRUD/edit.html.twig | 10 + 8 files changed, 355 insertions(+), 47 deletions(-) create mode 100644 Resources/views/CRUD/_edit_content.html.twig create mode 100644 Resources/views/CRUD/_edit_title.html.twig create mode 100644 Resources/views/CRUD/edit.html.twig diff --git a/CRUD/Controller/CRUDController.php b/CRUD/Controller/CRUDController.php index 9153833e8..5e18cc47b 100644 --- a/CRUD/Controller/CRUDController.php +++ b/CRUD/Controller/CRUDController.php @@ -20,48 +20,46 @@ namespace Chill\MainBundle\CRUD\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Pagination\PaginatorFactory; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; /** * * */ -abstract class CRUDController extends Controller +class CRUDController extends AbstractController { /** * * @var PaginatorFactory */ protected $paginatorFactory; - - abstract protected function getEntity(): string; - - abstract protected function orderingOptions(): array; + + /** + * The crud configuration + * + * This configuration si defined by `chill_main['crud']`. + * + * @var array + */ + protected $crudConfig; + + public function setCrudConfig(array $config) + { + $this->crudConfig = $config; + } protected function getDefaultOrdering(): array { return $this->orderingOptions(); } - protected function getTemplate($action): string - { - switch($action) { - case 'index': - return '@ChillMain\CRUD\index.html.twig'; - default: - throw new LogicException("action not supported: $action"); - } - } - - protected function getTemplateParameters($action): array - { - return []; - } - protected function processTemplateParameters($action): array { $configured = $this->getTemplateParameters($action); @@ -125,4 +123,274 @@ abstract class CRUDController extends Controller ], $this->processTemplateParameters('index')) ); } + + public function edit(Request $request, $id): Response + { + return $this->formEditAction('edit', $request, $id); + } + + protected function formEditAction($action, Request $request, $id, $formClass = null): Response + { + $entity = $this->getEntity($id, $request); + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found"), $this->getCrudName(), $id); + } + + $this->checkACL($action, $entity); + + $form = $this->createFormFor($action, $entity, $formClass); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + + $this->addFlash('succes', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforeRedirect($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index', [ + 'id' => $entity->getId() + ]); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + protected function formCreateAction($action, Request $request, $formClass = null): Response + { + $entity = $this->createEntity($request); + + $this->checkACL($action, $entity); + + $form = $this->createFormFor($action, $entity, $formClass); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPrePersist($action, $entity, $form, $request); + $em->persist($entity); + $this->onPostPersist($action, $entity, $form, $request); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + $this->getPaginatorFactory(); + $this->addFlash('succes', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforRedirect($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->get, ['id' => $entity->getId()]); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity + ]; + + return $this->render( + $this->getTemplateFor($action), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + /** + * get the instance of the entity with the given id + * + * @param string $id + * @return object + */ + protected function getEntity($id, Request $request): ?object + { + return $this->getDoctrine() + ->getRepository($this->getEntityClass()) + ->find($id); + } + + protected function getEntityClass(): string + { + return $this->crudConfig['class']; + } + + protected function getCrudName(): string + { + return $this->crudConfig['name']; + } + + protected function checkACL($entity, $action) + { + $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); + } + + protected function getRoleFor($action) + { + return $this->buildDefaultRole($action); + } + + 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']. + '_'. + $action); + } + + protected function getFormClassFor($action) + { + return $this->crudConfig[$action]['form_class'] + ?? $this->crudConfig['form_class']; + } + + protected function createFormFor($action, $entity, $formClass = null) + { + $formClass = $formClass ?? $this->getFormClassFor($action); + + $form = $this->createForm($formClass, $entity); + $form->add('submit', SubmitType::class, [ + 'label' => $action + ]); + + return $form; + } + + protected function generateFormErrorMessage($action, FormInterface $form): string + { + $msg = 'This form contains errors'; + + return $this->getTranslator()->trans($msg); + } + + protected function generateFormSuccessMessage($action, $entity): string + { + switch ($action) { + case 'edit': + $msg = "The data have been successfully updated"; + break; + case 'new': + $msg = "The date have been successfully created"; + break; + default: + $msg = "Your request has been successfully executed"; + } + + return $this->getTranslator()->trans($msg); + } + + protected function generateTemplateParameter( + $action, + $entity, + Request $request, + array $defaultTemplateParameters = [] + ) { + return $defaultTemplateParameters; + } + + protected function createEntity(Request $request): object + { + $type = $this->getEntityClass(); + return new $type; + } + + protected function getTemplateFor($action, $entity, Request $request) + { + if (!empty($this->crudConfig[$action]['template'])) { + return $this->crudConfig[$action]['template']; + } + + switch ($action) { + case 'new': + return '@ChillMain/CRUD/new.html.twig'; + case 'edit': + return '@ChillMain/CRUD/edit.html.twig'; + case 'index': + return '@ChillMain/CRUD/index.html.twig'; + default: + throw new \LogicException("the view for action $action is not " + . "defined. You should override ".__METHOD__." to add this " + . "action"); + } + } + + protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onFormValid(object $entity, FormInterface $form, Request $request) + { + } + + protected function onBeforeRedirect(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function getPaginatorFactory(): PaginatorFactory + { + return $this->get(PaginatorFactory::class); + } + + protected function getTranslator(): TranslatorInterface + { + return $this->container->get('translator'); + } + + public static function getSubscribedServices() + { + return \array_merge( + parent::getSubscribedServices(), + [ + PaginatorFactory::class => PaginatorFactory::class, + 'translator' => TranslatorInterface::class, + ] + ); + } } diff --git a/CRUD/Routing/CRUDRoutesLoader.php b/CRUD/Routing/CRUDRoutesLoader.php index 669401ce9..adc5d6420 100644 --- a/CRUD/Routing/CRUDRoutesLoader.php +++ b/CRUD/Routing/CRUDRoutesLoader.php @@ -25,7 +25,7 @@ use Symfony\Component\Routing\RouteCollection; /** - * + * Load the route for CRUD * */ class CRUDRoutesLoader diff --git a/Controller/AdminCountryCRUDController.php b/Controller/AdminCountryCRUDController.php index 00ce5c9d8..310a36c60 100644 --- a/Controller/AdminCountryCRUDController.php +++ b/Controller/AdminCountryCRUDController.php @@ -17,27 +17,4 @@ class AdminCountryCRUDController extends CRUDController { $this->paginatorFactory = $paginator; } - - protected function getEntity(): string - { - return Country::class; - } - - protected function orderingOptions(): array - { - return [ - 'countryCode' => 'ASC' - ]; - } - - protected function getTemplateParameters($action): array - { - switch ($action) { - case 'index': - return [ - 'columns' => [ 'countryCode', 'name' ], - 'title' => 'Liste des pays' - ]; - } - } } diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php index 6e7c22581..92bc8ce9c 100644 --- a/DependencyInjection/ChillMainExtension.php +++ b/DependencyInjection/ChillMainExtension.php @@ -221,5 +221,28 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, ; $container->setDefinition('chill_main_crud_route_loader', $definition); + + $alreadyExistingNames = []; + + foreach ($config as $crudEntry) { + $controller = $crudEntry['controller']; + $name = $crudEntry['name']; + + // check for existing crud names + if (\in_array($name, $alreadyExistingNames)) { + throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name)); + } + + if (!$container->has($controller)) { + $controllerDefinition = new Definition($controller); + $controllerDefinition->addTag('controller.service_arguments'); + $controllerDefinition->setAutoconfigured(true); + $container->setDefinition($controller, $controllerDefinition); + } + + $container->setParameter('chill_main_crud_config_'.$name, $crudEntry); + $container->getDefinition($controller) + ->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']); + } } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index c901fca40..089ed42dc 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -124,10 +124,11 @@ class Configuration implements ConfigurationInterface ->scalarNode('controller')->cannotBeEmpty()->isRequired()->end() ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_role')->defaultNull()->end() + ->scalarNode('form_class')->defaultNull()->end() ->arrayNode('actions') ->scalarPrototype()->end() - //->defaultValue(['index', 'new', 'edit', 'show', 'delete']) - //->ifEmpty()->thenInvalid() + ->defaultValue(['index', 'new', 'edit', 'show', 'delete']) ->end() ->end() ->end() diff --git a/Resources/views/CRUD/_edit_content.html.twig b/Resources/views/CRUD/_edit_content.html.twig new file mode 100644 index 000000000..f8afadcba --- /dev/null +++ b/Resources/views/CRUD/_edit_content.html.twig @@ -0,0 +1,28 @@ +
+ {% block crud_content_header %} +

{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }}

+ {% endblock crud_content_header %} + + {% block crud_content_form %} + {{ form_start(form) }} + {% for f in form if f.vars.name != 'submit' %} + {{ form_row(f) }} + {% endfor %} + + {% block crud_content_form_actions %} +
+
    + {% block content_form_actions_back %} +
  • + + {{ 'Cancel'|trans }} + + {% endblock %} +
  • {{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-edit'} } ) }}
  • +
+
+ {% endblock %} + + {{ form_end(form) }} + {% endblock %} +
diff --git a/Resources/views/CRUD/_edit_title.html.twig b/Resources/views/CRUD/_edit_title.html.twig new file mode 100644 index 000000000..c98b7f2cd --- /dev/null +++ b/Resources/views/CRUD/_edit_title.html.twig @@ -0,0 +1 @@ +{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }} diff --git a/Resources/views/CRUD/edit.html.twig b/Resources/views/CRUD/edit.html.twig new file mode 100644 index 000000000..ecd68ef02 --- /dev/null +++ b/Resources/views/CRUD/edit.html.twig @@ -0,0 +1,10 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %} +{% include('@ChillMain/CRUD/_edit_title.html.twig') %} +{% endblock %} + +{% block content %} +{% embed '@ChillMain/CRUD/_edit_content.html.twig' %} +{% endembed %} +{% endblock %} From 1860a6bae4fd456ee70f325441a3861fda754ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 3 Dec 2019 15:04:22 +0100 Subject: [PATCH 03/43] add show-hide module for javascript --- CHANGELOG.md | 6 ++ Resources/public/modules/show_hide/index.js | 1 + .../public/modules/show_hide/show_hide.js | 82 +++++++++++++++++++ Resources/public/sass/_custom.scss | 2 +- .../sass/contrib/fontawesome/_path.scss | 12 +-- .../PrintOrMessage/blockquote_date.html.twig | 1 + .../PrintOrMessage/default_date.html.twig | 1 + Templating/ChillTwigHelper.php | 47 ++++++++--- chill.webpack.config.js | 2 + 9 files changed, 135 insertions(+), 19 deletions(-) create mode 100644 Resources/public/modules/show_hide/index.js create mode 100644 Resources/public/modules/show_hide/show_hide.js create mode 100644 Resources/views/Extensions/PrintOrMessage/blockquote_date.html.twig create mode 100644 Resources/views/Extensions/PrintOrMessage/default_date.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 343879a3c..5d1f713c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,3 +65,9 @@ Version 1.5.10 - allow to group export in UI +Master branch +============= + +- improve `chill_print_or_message` to support date time; +- add a module `show_hide` for javascript; + diff --git a/Resources/public/modules/show_hide/index.js b/Resources/public/modules/show_hide/index.js new file mode 100644 index 000000000..34f3b80b5 --- /dev/null +++ b/Resources/public/modules/show_hide/index.js @@ -0,0 +1 @@ +require("./show_hide.js"); \ No newline at end of file diff --git a/Resources/public/modules/show_hide/show_hide.js b/Resources/public/modules/show_hide/show_hide.js new file mode 100644 index 000000000..a634df212 --- /dev/null +++ b/Resources/public/modules/show_hide/show_hide.js @@ -0,0 +1,82 @@ +/** + * Create a control to show or hide values + * + * Possible options are: + * + * - froms: an Element, an Array of Element, or a NodeList. A + * listener will be attached to **all** input of those elements + * and will trigger the check on changes + * - test: a function which will test the element and will return true + * if the content must be shown, false if it must be hidden. + * The function will receive the `froms` as first argument, and the + * event as second argument. + * - container: an Element, an Array of Element, or a Node List. The + * child nodes will be hidden / shown inside this container + * - event_name: the name of the event to listen to. `'change'` by default. + * + * @param object options + */ +var ShowHide = function(options) { + var + froms = typeof options.froms[Symbol.iterator] === "function" ? options.froms : [ options.froms ], //options.froms; + test = options.test, + container = typeof options.container[Symbol.iterator] === "function" ? options.container : [ options.container ], + is_shown = true, + event_name = 'event_name' in options ? options.event_name : 'change', + container_content = []; + + window.addEventListener('load', function(event) { + // keep the content in memory + for (let c of container.values()) { + let contents = []; + for (let el of c.childNodes.values()) { + contents.push(el); + } + container_content.push(contents); + } + + // attach the listener on each input + for (let f of froms.values()) { + let inputs = f.querySelectorAll('input'); + for (let input of inputs.values()) { + input.addEventListener(event_name, function(e) { + onChange(e); + }); + } + } + + // first launch of the show/hide + onChange(event); + }); + + + var onChange = function (event) { + var result = test(froms, event); + + if (result === true) { + if (is_shown === false) { + for (let i of container_content.keys()) { + var contents = container_content[i]; + for (let el of contents.values()) { + container[i].appendChild(el); + } + } + is_shown = true; + } + } else if (result === false) { + if (is_shown) { + for (let contents of container_content.values()) { + for (let el of contents.values()) { + el.remove(); + } + } + is_shown = false; + } + } else { + throw "the result of test is not a boolean"; + } + + }; +}; + +export {ShowHide}; diff --git a/Resources/public/sass/_custom.scss b/Resources/public/sass/_custom.scss index 59617dafe..867d1b071 100644 --- a/Resources/public/sass/_custom.scss +++ b/Resources/public/sass/_custom.scss @@ -38,7 +38,7 @@ header { right: 0; top: 0; z-index: -1; - background-image: url('./../../img/background/desert.jpg'); + //background-image: url('./../../img/background/desert.jpg'); background-attachment: fixed; background-repeat: no-repeat; background-size: cover; diff --git a/Resources/public/sass/contrib/fontawesome/_path.scss b/Resources/public/sass/contrib/fontawesome/_path.scss index bb457c23a..949ad920b 100644 --- a/Resources/public/sass/contrib/fontawesome/_path.scss +++ b/Resources/public/sass/contrib/fontawesome/_path.scss @@ -3,12 +3,12 @@ @font-face { font-family: 'FontAwesome'; - src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); - src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), - url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), - url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), - url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), - url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); + src: url('./../../../fonts/fontawesome-webfont.eot?v=#{$fa-version}'); + src: url('./../../../fonts/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), + url('./../../../fonts/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), + url('./../../../fonts/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), + url('./../../../fonts/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), + url('./../../../fonts/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts font-weight: normal; font-style: normal; diff --git a/Resources/views/Extensions/PrintOrMessage/blockquote_date.html.twig b/Resources/views/Extensions/PrintOrMessage/blockquote_date.html.twig new file mode 100644 index 000000000..3a37a6850 --- /dev/null +++ b/Resources/views/Extensions/PrintOrMessage/blockquote_date.html.twig @@ -0,0 +1 @@ +{% if value is not empty %}
{{ value|localizeddate(date_format, time_format) }}
{% else %}{{ message|trans }}{% endif %} \ No newline at end of file diff --git a/Resources/views/Extensions/PrintOrMessage/default_date.html.twig b/Resources/views/Extensions/PrintOrMessage/default_date.html.twig new file mode 100644 index 000000000..e2f50b083 --- /dev/null +++ b/Resources/views/Extensions/PrintOrMessage/default_date.html.twig @@ -0,0 +1 @@ +{% if value is not empty %}{{ value|localizeddate(date_format, time_format) }}{% else %}{{ message|trans }}{% endif %} \ No newline at end of file diff --git a/Templating/ChillTwigHelper.php b/Templating/ChillTwigHelper.php index 7e665f8a3..4f4b6dc0a 100644 --- a/Templating/ChillTwigHelper.php +++ b/Templating/ChillTwigHelper.php @@ -33,30 +33,53 @@ class ChillTwigHelper extends AbstractExtension * - 'default' ; * - 'blockquote' ; * + * `DateTimeInterface are also rendered. The date and time format may be set + * using those key in `$options´ parameter: + * + * - `date_format` (default to `'medium'`) + * - `time_format` (default to `'none'`) + * * @param Environment $twig - * @param string $value + * @param string $value Default to 'No value'. Fallback to default if null * @param string $message * @param string $template + * @param array $options * @return string */ public function printOrMessage( Environment $twig, $value, $message = 'No value', - $template = 'default' + $template = 'default', + array $options = [] ) { - switch ($template) { - case 'default': - case 'blockquote': - $t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'.html.twig'; - break; - default: - $t = $template; + if ($value instanceof \DateTimeInterface) { + $options = \array_merge([ + 'date_format' => 'medium', + 'time_format' => 'none' + ], $options); + switch ($template) { + case 'default': + case 'blockquote': + $t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'_date.html.twig'; + break; + default: + $t = $template; + } + } else { + switch ($template) { + case 'default': + case 'blockquote': + $t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'.html.twig'; + break; + default: + $t = $template; + } } - return $twig->render($t, [ + return $twig->render($t, \array_merge([ 'value' => $value, - 'message' => $message - ]); + 'message' => $message ?? 'No value' + ], $options)); } } diff --git a/chill.webpack.config.js b/chill.webpack.config.js index 9fb9200a7..9240c2f6a 100644 --- a/chill.webpack.config.js +++ b/chill.webpack.config.js @@ -29,6 +29,8 @@ require('./Resources/public/modules/download-report/index.js'); require('select2/dist/css/select2.css'); require('./Resources/public/modules/select_interactive_loading/index.js'); require('./Resources/public/modules/export-list/export-list.scss'); +//import {ChillShowHide} from './Resources/public/modules/show_hide/index.js'; +//global.ChillShowHide = ChillShowHide; // img require('./Resources/public/img/favicon.ico'); From 257d101fbe605f376c3cdb3f60db9e29b1eb6ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 3 Dec 2019 15:04:39 +0100 Subject: [PATCH 04/43] [wip] CRUD --- CRUD/Controller/CRUDController.php | 103 ++++++++++++++++--- CRUD/Routing/CRUDRoutesLoader.php | 12 +-- DependencyInjection/Configuration.php | 27 ++++- Resources/translations/messages.fr.yml | 1 + Resources/views/CRUD/_edit_content.html.twig | 24 +++-- 5 files changed, 136 insertions(+), 31 deletions(-) diff --git a/CRUD/Controller/CRUDController.php b/CRUD/Controller/CRUDController.php index 5e18cc47b..8df566f0d 100644 --- a/CRUD/Controller/CRUDController.php +++ b/CRUD/Controller/CRUDController.php @@ -28,6 +28,9 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Symfony\Component\Form\FormInterface; use Symfony\Component\Translation\TranslatorInterface; 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; /** * @@ -129,9 +132,42 @@ class CRUDController extends AbstractController return $this->formEditAction('edit', $request, $id); } - protected function formEditAction($action, Request $request, $id, $formClass = null): Response + public function view(Request $request, $id): Response { - $entity = $this->getEntity($id, $request); + return $this->viewAction('view', $request, $id); + } + + protected function viewAction($action, Request $request, $id) + { + $entity = $this->getEntity($action, $id, $request); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity); + + if ($postFetch instanceof Response) { + return $postFetch; + } + + $this->checkACL($action, $entity); + + $postCheckACL = $this->onPostCheckACL($action, $request, $entity); + + if ($postCheckACL instanceof Response) { + return $postCheckACL; + } + + $defaultTemplateParameters = [ + 'entity' => $entity + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + protected function formEditAction($action, Request $request, $id, $formClass = null, $formOptions = []): Response + { + $entity = $this->getEntity($action, $id, $request); if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " @@ -140,7 +176,7 @@ class CRUDController extends AbstractController $this->checkACL($action, $entity); - $form = $this->createFormFor($action, $entity, $formClass); + $form = $this->createFormFor($action, $entity, $formClass, $formOptions); $form->handleRequest($request); @@ -165,7 +201,7 @@ class CRUDController extends AbstractController ]); } elseif ($form->isSubmitted()) { - $this->addFlash('error', $this->generateFormErrorMessage($form)); + $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } $defaultTemplateParameters = [ @@ -213,7 +249,7 @@ class CRUDController extends AbstractController return $this->redirectToRoute('chill_crud_'.$this->get, ['id' => $entity->getId()]); } elseif ($form->isSubmitted()) { - $this->addFlash('error', $this->generateFormErrorMessage($form)); + $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } $defaultTemplateParameters = [ @@ -233,11 +269,14 @@ class CRUDController extends AbstractController * @param string $id * @return object */ - protected function getEntity($id, Request $request): ?object + protected function getEntity($action, $id, Request $request): ?object { return $this->getDoctrine() ->getRepository($this->getEntityClass()) ->find($id); + + + } protected function getEntityClass(): string @@ -279,18 +318,29 @@ class CRUDController extends AbstractController ?? $this->crudConfig['form_class']; } - protected function createFormFor($action, $entity, $formClass = null) + protected function createFormFor($action, $entity, $formClass = null, $formOptions = []) { $formClass = $formClass ?? $this->getFormClassFor($action); - $form = $this->createForm($formClass, $entity); - $form->add('submit', SubmitType::class, [ - 'label' => $action - ]); + $form = $this->createForm($formClass, $entity, $formOptions); + + $this->addDefaultButtons($action, $form); return $form; } + protected function addDefaultButtons($action, FormInterface $form) + { + $form->add('submit', SubmitType::class, [ + 'label' => $this->generateLabelForButton($action, 'submit', $form) + ]); + } + + protected function generateLabelForButton($action, $formName, $form) + { + return $action; + } + protected function generateFormErrorMessage($action, FormInterface $form): string { $msg = 'This form contains errors'; @@ -365,6 +415,16 @@ class CRUDController extends AbstractController { } + protected function onPostFetchEntity($action, Request $request, $entity): ?Response + { + return null; + } + + protected function onPostCheckACL($action, Request $request, $entity): ?Response + { + return null; + } + protected function onFormValid(object $entity, FormInterface $form, Request $request) { } @@ -383,6 +443,23 @@ class CRUDController extends AbstractController return $this->container->get('translator'); } + protected function getAuthorizationHelper(): AuthorizationHelper + { + return $this->container->get(AuthorizationHelper::class); + } + + protected function getReachableCenters(Role $role, Scope $scope = null) + { + return $this->getAuthorizationHelper() + ->getReachableCenters($this->getUser(), $role, $scope) + ; + } + + protected function getEventDispatcher(): EventDispatcherInterface + { + return $this->get(EventDispatcherInterface::class); + } + public static function getSubscribedServices() { return \array_merge( @@ -390,7 +467,9 @@ class CRUDController extends AbstractController [ PaginatorFactory::class => PaginatorFactory::class, 'translator' => TranslatorInterface::class, + AuthorizationHelper::class => AuthorizationHelper::class, + EventDispatcherInterface::class => EventDispatcherInterface::class, ] - ); + ); } } diff --git a/CRUD/Routing/CRUDRoutesLoader.php b/CRUD/Routing/CRUDRoutesLoader.php index adc5d6420..3ff7959d3 100644 --- a/CRUD/Routing/CRUDRoutesLoader.php +++ b/CRUD/Routing/CRUDRoutesLoader.php @@ -51,7 +51,7 @@ class CRUDRoutesLoader public function load() { $collection = new RouteCollection(); - + foreach ($this->config as $config) { $collection->addCollection($this->loadConfig($config)); } @@ -63,23 +63,23 @@ class CRUDRoutesLoader { $collection = new RouteCollection(); - foreach ($config['actions'] as $action) { + foreach ($config['actions'] as $name => $action) { $defaults = [ - '_controller' => $config['controller'].'::'.$action + '_controller' => $action['controller'] ?? $config['controller'].'::'.$name ]; if ($action === 'index') { $path = "{_locale}".$config['base_path']; $route = new Route($path, $defaults); } else { - $path = "{_locale}".$config['base_path'].'/{id}/'.$action; - $requirements = [ + $path = "{_locale}".$config['base_path'].($action['path'] ?? '/{id}/'.$name); + $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; $route = new Route($path, $defaults, $requirements); } - $collection->add('chill_crud_'.$config['name'].'_'.$action, $route); + $collection->add('chill_crud_'.$config['name'].'_'.$name, $route); } return $collection; diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 089ed42dc..1e4d410ce 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -127,9 +127,32 @@ class Configuration implements ConfigurationInterface ->scalarNode('base_role')->defaultNull()->end() ->scalarNode('form_class')->defaultNull()->end() ->arrayNode('actions') - ->scalarPrototype()->end() - ->defaultValue(['index', 'new', 'edit', 'show', 'delete']) + ->defaultValue([ + 'edit' => [], + 'new' => [] + ]) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('controller') + ->defaultValue('') + ->info('the method name to call in the route. Will be set to the action name if left empty.') + ->example("'MyBundle\Controller\MyCrudController::action'") + ->end() + ->scalarNode('path') + ->defaultValue('') + ->info('the path that will be **appended** after the base path. Do not forget to add ' + . 'arguments for the method. Will be set to the action name, including an `{id}` ' + . 'parameter if left empty.') + ->example('/{id}/my-action') + ->end() + ->arrayNode('requirements') + ->ignoreExtraKeys(false) + ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') + ->end() + ->end() ->end() + ->end() ->end() ->end() diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 6b7cda525..b8990fca0 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -34,6 +34,7 @@ Save: Enregistrer This form contains errors: Ce formulaire contient des erreurs Choose an user: Choisir un utilisateur 'You are going to leave a page with unsubmitted data. Are you sure you want to leave ?': "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?" +No value: Aucune information Edit: Modifier Update: Mettre à jour diff --git a/Resources/views/CRUD/_edit_content.html.twig b/Resources/views/CRUD/_edit_content.html.twig index f8afadcba..9dcb7820a 100644 --- a/Resources/views/CRUD/_edit_content.html.twig +++ b/Resources/views/CRUD/_edit_content.html.twig @@ -5,22 +5,24 @@ {% block crud_content_form %} {{ form_start(form) }} + + {% block crud_content_form_rows %} {% for f in form if f.vars.name != 'submit' %} {{ form_row(f) }} {% endfor %} + {% endblock crud_content_form_rows %} {% block crud_content_form_actions %} -
-
    - {% block content_form_actions_back %} -
  • - - {{ 'Cancel'|trans }} - - {% endblock %} -
  • {{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-edit'} } ) }}
  • -
-
+
    + {% block content_form_actions_back %} +
  • + + {{ 'Cancel'|trans }} + +
  • + {% endblock %} +
  • {{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-edit'} } ) }}
  • +
{% endblock %} {{ form_end(form) }} From 73bb897e61b73097b1ea9bf2d7b48fbaa06ae7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 4 Dec 2019 14:05:35 +0100 Subject: [PATCH 05/43] configure assets using function + use `runtime.js` --- CHANGELOG.md | 7 +++-- Resources/public/main.js | 38 +++++++++++++++++++++++ Resources/views/Login/login.html.twig | 3 +- Resources/views/layout.html.twig | 1 + chill.webpack.config.js | 44 +++++---------------------- 5 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 Resources/public/main.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1f713c5..db246efb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,9 +65,10 @@ Version 1.5.10 - allow to group export in UI -Master branch -============= +Branch CRUD-Init +================ - improve `chill_print_or_message` to support date time; - add a module `show_hide` for javascript; - +- load assets using functions ; +- load a `runtime.js` assets for objects shared by webpack ; diff --git a/Resources/public/main.js b/Resources/public/main.js new file mode 100644 index 000000000..50d4e9740 --- /dev/null +++ b/Resources/public/main.js @@ -0,0 +1,38 @@ +// import jQuery +const $ = require('jquery'); +// create global $ and jQuery variables +global.$ = global.jQuery = $; + +const moment = require('moment'); +global.moment = moment; + +const pikaday = require('pikaday-jquery'); + +const select2 = require('select2'); +global.select2 = select2; + +// import js +import {chill} from './js/chill.js'; +global.chill = chill; + +// css +require('./sass/scratch.scss'); +require('./css/chillmain.css'); +require('./css/pikaday.css'); +require('./js/collection/collections.js'); +require('./modules/breadcrumb/index.js'); +require('./modules/download-report/index.js'); +//require('./css/scratch.css'); +//require('./css/select2/select2.css'); +require('select2/dist/css/select2.css'); +require('./modules/select_interactive_loading/index.js'); +require('./modules/export-list/export-list.scss'); +//import {ChillShowHide} from './modules/show_hide/index.js'; +//global.ChillShowHide = ChillShowHide; + +// img +require('./img/favicon.ico'); +require('./img/logo-chill-sans-slogan_white.png'); +require('./img/logo-chill-outil-accompagnement_white.png'); + + diff --git a/Resources/views/Login/login.html.twig b/Resources/views/Login/login.html.twig index 114188f75..54d5ae26c 100644 --- a/Resources/views/Login/login.html.twig +++ b/Resources/views/Login/login.html.twig @@ -15,7 +15,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} - @@ -48,5 +47,7 @@ + + diff --git a/Resources/views/layout.html.twig b/Resources/views/layout.html.twig index 2f69e26a6..3029516d5 100644 --- a/Resources/views/layout.html.twig +++ b/Resources/views/layout.html.twig @@ -142,6 +142,7 @@ {{ include('@ChillMain/Layout/_footer.html.twig') }} + + diff --git a/Resources/views/layout.html.twig b/Resources/views/layout.html.twig index 920badebb..9baba0fb5 100644 --- a/Resources/views/layout.html.twig +++ b/Resources/views/layout.html.twig @@ -142,6 +142,7 @@ {{ include('@ChillMain/Layout/_footer.html.twig') }} + From 199930d23a4ec4ae909c56f84c0fb9c1289763da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 11 Dec 2019 16:08:16 +0100 Subject: [PATCH 13/43] [wip] add actions to crud --- CHANGELOG.md | 1 + CRUD/Controller/CRUDController.php | 79 ++++++++++++------- CRUD/Resolver/Resolver.php | 53 ++++++++++++- CRUD/Templating/TwigCRUDResolver.php | 14 ++++ DependencyInjection/Configuration.php | 4 + Resources/config/services/crud.yml | 1 + .../public/sass/custom/_record_actions.scss | 1 + .../public/sass/custom/modules/_buttons.scss | 15 +++- .../public/sass/custom/modules/copy-solid.svg | 4 + Resources/translations/messages.fr.yml | 21 ++++- Resources/views/CRUD/_edit_content.html.twig | 28 ++++++- Resources/views/CRUD/_edit_title.html.twig | 2 +- Resources/views/CRUD/_new_content.html.twig | 22 +++++- Resources/views/CRUD/_view_content.html.twig | 46 +++++++++++ Resources/views/CRUD/_view_title.html.twig | 1 + Resources/views/CRUD/view.html.twig | 10 +++ 16 files changed, 265 insertions(+), 37 deletions(-) create mode 100644 Resources/public/sass/custom/modules/copy-solid.svg create mode 100644 Resources/views/CRUD/_view_content.html.twig create mode 100644 Resources/views/CRUD/_view_title.html.twig create mode 100644 Resources/views/CRUD/view.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 355a69d02..da3b1c469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,3 +96,4 @@ Version 1.5.14 Branch CRUD-Init ================ +- create an api for rendering entities diff --git a/CRUD/Controller/CRUDController.php b/CRUD/Controller/CRUDController.php index 85bf34493..70f6e9a75 100644 --- a/CRUD/Controller/CRUDController.php +++ b/CRUD/Controller/CRUDController.php @@ -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, ] ); } diff --git a/CRUD/Resolver/Resolver.php b/CRUD/Resolver/Resolver.php index 0eac94d01..2475efd42 100644 --- a/CRUD/Resolver/Resolver.php +++ b/CRUD/Resolver/Resolver.php @@ -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: diff --git a/CRUD/Templating/TwigCRUDResolver.php b/CRUD/Templating/TwigCRUDResolver.php index ca398814c..9c83df864 100644 --- a/CRUD/Templating/TwigCRUDResolver.php +++ b/CRUD/Templating/TwigCRUDResolver.php @@ -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); + } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index bb15f3b42..f718ecdd9 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -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() diff --git a/Resources/config/services/crud.yml b/Resources/config/services/crud.yml index ab686d34e..478122b73 100644 --- a/Resources/config/services/crud.yml +++ b/Resources/config/services/crud.yml @@ -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: diff --git a/Resources/public/sass/custom/_record_actions.scss b/Resources/public/sass/custom/_record_actions.scss index 3cd9fc9a5..64ee66077 100644 --- a/Resources/public/sass/custom/_record_actions.scss +++ b/Resources/public/sass/custom/_record_actions.scss @@ -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; diff --git a/Resources/public/sass/custom/modules/_buttons.scss b/Resources/public/sass/custom/modules/_buttons.scss index b465742f7..e070dcd57 100644 --- a/Resources/public/sass/custom/modules/_buttons.scss +++ b/Resources/public/sass/custom/modules/_buttons.scss @@ -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 { diff --git a/Resources/public/sass/custom/modules/copy-solid.svg b/Resources/public/sass/custom/modules/copy-solid.svg new file mode 100644 index 000000000..6acdf87cd --- /dev/null +++ b/Resources/public/sass/custom/modules/copy-solid.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index b8990fca0..93c2c303d 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/Resources/views/CRUD/_edit_content.html.twig b/Resources/views/CRUD/_edit_content.html.twig index 9dcb7820a..0ede64748 100644 --- a/Resources/views/CRUD/_edit_content.html.twig +++ b/Resources/views/CRUD/_edit_content.html.twig @@ -1,13 +1,13 @@
{% block crud_content_header %} -

{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }}

+

{{ ('crud.'~crud_name~'.title_edit')|trans }}

{% 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 }} + {% endblock %} + {% block content_form_actions_view %} + {% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %} +
  • + + {{ 'crud.edit.back_to_view'|trans }} + +
  • + {% endif %} {% endblock %} -
  • {{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-edit'} } ) }}
  • + {% block content_form_actions_save_and_close %} +
  • + +
  • + {% endblock %} + {% block content_form_actions_save_and_show %} +
  • + +
  • + {% endblock %} {% endblock %} diff --git a/Resources/views/CRUD/_edit_title.html.twig b/Resources/views/CRUD/_edit_title.html.twig index c98b7f2cd..7561ceda4 100644 --- a/Resources/views/CRUD/_edit_title.html.twig +++ b/Resources/views/CRUD/_edit_title.html.twig @@ -1 +1 @@ -{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }} +{{ ('crud.'~crud_name~'.title_edit')|trans }} diff --git a/Resources/views/CRUD/_new_content.html.twig b/Resources/views/CRUD/_new_content.html.twig index c01969e24..3da85d3ad 100644 --- a/Resources/views/CRUD/_new_content.html.twig +++ b/Resources/views/CRUD/_new_content.html.twig @@ -21,7 +21,27 @@ {% endblock %} -
  • {{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-new'} } ) }}
  • + {% block content_form_actions_save_and_close %} +
  • + +
  • + {% endblock %} + {% block content_form_actions_save_and_show %} +
  • + +
  • + {% endblock %} + {% block content_form_actions_save_and_new %} +
  • + +
  • + {% endblock %} {% endblock %} diff --git a/Resources/views/CRUD/_view_content.html.twig b/Resources/views/CRUD/_view_content.html.twig new file mode 100644 index 000000000..39cf85359 --- /dev/null +++ b/Resources/views/CRUD/_view_content.html.twig @@ -0,0 +1,46 @@ +
    + {% block crud_content_header %} +

    {{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}

    + {% endblock crud_content_header %} + + {% block crud_content_view %} + + {% block crud_content_view_details %} +
    +
    id
    +
    {{ entity.id|default("No id") }}
    +
    + {% endblock crud_content_view_details %} + + {% block crud_content_view_actions %} +
      + {% block content_view_actions_back %} +
    • + + {{ 'Cancel'|trans }} + +
    • + {% endblock %} + {% block content_view_actions_duplicate_link %} + {% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %} +
    • + + {{ 'crud.view.link_duplicate'|trans }} + +
    • + {% endif %} + {% endblock content_view_actions_duplicate_link %} + {% block content_view_actions_edit_link %} + {% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %} +
    • + + {{ 'crud.new.link_edit'|trans }} + +
    • + {% endif %} + {% endblock content_view_actions_edit_link %} +
    + {% endblock crud_content_view_actions %} + + {% endblock crud_content_view %} +
    diff --git a/Resources/views/CRUD/_view_title.html.twig b/Resources/views/CRUD/_view_title.html.twig new file mode 100644 index 000000000..3473dd298 --- /dev/null +++ b/Resources/views/CRUD/_view_title.html.twig @@ -0,0 +1 @@ +{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }} \ No newline at end of file diff --git a/Resources/views/CRUD/view.html.twig b/Resources/views/CRUD/view.html.twig new file mode 100644 index 000000000..f32234a33 --- /dev/null +++ b/Resources/views/CRUD/view.html.twig @@ -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 %} From dbec09d93779c92928c11b2a7fefd65dfefa56f2 Mon Sep 17 00:00:00 2001 From: Tchama Date: Thu, 12 Dec 2019 11:58:02 +0100 Subject: [PATCH 14/43] tabs feature, testing context --- Controller/DefaultController.php | 5 +++++ Resources/config/routing.yml | 4 ++++ Resources/public/modules/tabs/index.js | 2 ++ Resources/public/modules/tabs/tabs.js | 1 + Resources/public/modules/tabs/tabs.scss | 6 +++++ Resources/views/Test/index.html.twig | 29 +++++++++++++++++++++++++ chill.webpack.config.js | 1 + 7 files changed, 48 insertions(+) create mode 100644 Resources/public/modules/tabs/index.js create mode 100644 Resources/public/modules/tabs/tabs.js create mode 100644 Resources/public/modules/tabs/tabs.scss create mode 100644 Resources/views/Test/index.html.twig diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php index b10bf0d23..dc6bc222a 100644 --- a/Controller/DefaultController.php +++ b/Controller/DefaultController.php @@ -20,4 +20,9 @@ class DefaultController extends Controller { return $this->redirect($this->generateUrl('chill_main_homepage')); } + + public function testAction() + { + return $this->render('ChillMainBundle:Test:index.html.twig', []); + } } \ No newline at end of file diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index c6ab5918e..ee3ca9516 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -88,3 +88,7 @@ login_check: logout: path: /logout + +chill_main_test: + path: /{_locale}/main/test + defaults: { _controller: ChillMainBundle:Default:test } \ No newline at end of file diff --git a/Resources/public/modules/tabs/index.js b/Resources/public/modules/tabs/index.js new file mode 100644 index 000000000..92ee9fd7b --- /dev/null +++ b/Resources/public/modules/tabs/index.js @@ -0,0 +1,2 @@ +require("./tabs.js"); +require("./tabs.scss"); diff --git a/Resources/public/modules/tabs/tabs.js b/Resources/public/modules/tabs/tabs.js new file mode 100644 index 000000000..d4064653f --- /dev/null +++ b/Resources/public/modules/tabs/tabs.js @@ -0,0 +1 @@ +alert('coucou'); \ No newline at end of file diff --git a/Resources/public/modules/tabs/tabs.scss b/Resources/public/modules/tabs/tabs.scss new file mode 100644 index 000000000..77e9bc54e --- /dev/null +++ b/Resources/public/modules/tabs/tabs.scss @@ -0,0 +1,6 @@ + + +div.tabs.test { + + font-size: 300%; +} \ No newline at end of file diff --git a/Resources/views/Test/index.html.twig b/Resources/views/Test/index.html.twig new file mode 100644 index 000000000..f1562bfa9 --- /dev/null +++ b/Resources/views/Test/index.html.twig @@ -0,0 +1,29 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %}Page de test{% endblock %} + +{% block css%} + +{% endblock %} + +{% block js%} + +{% endblock %} + +{% block content %} + +
    + test +
    + +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.

    + +

    Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.

    + +

    In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.

    + +

    Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.

    + +

    Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.

    + +{% endblock %} \ No newline at end of file diff --git a/chill.webpack.config.js b/chill.webpack.config.js index 572db8d6c..4baf22fa2 100644 --- a/chill.webpack.config.js +++ b/chill.webpack.config.js @@ -2,6 +2,7 @@ module.exports = function(encore, entries) { entries.push(__dirname + '/Resources/public/main.js'); encore.addEntry('login', './vendor/chill-project/main/Resources/public/modules/login_page/index.js'); + encore.addEntry('tabs', './vendor/chill-project/main/Resources/public/modules/tabs/index.js'); encore.addAliases({ ShowHide: __dirname + '/Resources/public/modules/show_hide/' }); From 7d5ea38ea3527336f5d0cb9adce05a23d52e4c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 13 Dec 2019 10:14:06 +0100 Subject: [PATCH 15/43] render the "no specified" item in choices as italic --- CHANGELOG.md | 1 + Resources/public/sass/modules/_forms.scss | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da3b1c469..970fa0fde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,3 +97,4 @@ Branch CRUD-Init ================ - create an api for rendering entities +- css: render the placeholder in expanded choice item as italic (the "no specified" choice") diff --git a/Resources/public/sass/modules/_forms.scss b/Resources/public/sass/modules/_forms.scss index 970d33a48..f2c533906 100644 --- a/Resources/public/sass/modules/_forms.scss +++ b/Resources/public/sass/modules/_forms.scss @@ -25,6 +25,11 @@ label { abbr { display: none; } + + // mark the label for empty placeholder + &[for$="_placeholder"] { + font-style: italic; + } } .inline-choice { From 600c48b1935964e83ce42d987790a0261da947fd Mon Sep 17 00:00:00 2001 From: Tchama Date: Thu, 12 Dec 2019 18:35:21 +0100 Subject: [PATCH 16/43] tabs feature, sass and javascript basic --- Resources/public/modules/tabs/tabs.js | 78 +++++++++++++++++- Resources/public/modules/tabs/tabs.scss | 96 +++++++++++++++++++++- Resources/views/Test/index.html.twig | 105 +++++++++++++++++++++--- 3 files changed, 266 insertions(+), 13 deletions(-) diff --git a/Resources/public/modules/tabs/tabs.js b/Resources/public/modules/tabs/tabs.js index d4064653f..77de95bff 100644 --- a/Resources/public/modules/tabs/tabs.js +++ b/Resources/public/modules/tabs/tabs.js @@ -1 +1,77 @@ -alert('coucou'); \ No newline at end of file +/* + * Remove active class on both elements: link and content + */ +let resetActive = function(links, contents) +{ + for (items of [links, contents]) { + items.forEach(function(item) { + if (item.classList.contains('active')) { + item.classList.remove('active'); + } + }); + } +} + +/* + * Count links array and return rank of given link + */ +let countNewActive = function(links, link) +{ + let rank = 0; + for (let i = 0; i < links.length; ++i) { + rank++; + if(links[i] == link) { + return rank; + } + } +} + +/* + * Set class active on both new elements: link and content + */ +let setNewActive = function(link, contents, rank) +{ + link.classList.add('active'); + + count = 0; + contents.forEach(function(pane) { + count++; + if (rank == count) { + pane.classList.add('active'); + } + }); +} + +/* + * Main function + */ +window.addEventListener('load', function() +{ + tabParams.forEach(function(unit) { + + let + tabPanel = document.querySelector('#'+ unit.id ), + nav = tabPanel.querySelector('nav'), + tabs = nav.querySelectorAll('ul.nav-tabs li.nav-item'), + links = nav.querySelectorAll('ul.nav-tabs li.nav-item a.nav-link'), + contents = tabPanel.querySelectorAll('div.tab-content div.tab-pane') + ; + + if (unit.type == 'pill') { + tabPanel.classList.add('pills'); + } + + // initial position + setNewActive(links[unit.startPanel-1], contents, unit.startPanel); + + // listen + links.forEach(function(link) { + link.addEventListener('click', function() + { + resetActive(links, contents); + setNewActive(link, contents, countNewActive(links, link)); + }); + }); + + }); +}); \ No newline at end of file diff --git a/Resources/public/modules/tabs/tabs.scss b/Resources/public/modules/tabs/tabs.scss index 77e9bc54e..96fce3f88 100644 --- a/Resources/public/modules/tabs/tabs.scss +++ b/Resources/public/modules/tabs/tabs.scss @@ -1,6 +1,98 @@ +$navigation-color: #334d5c; +$body-font-color: #ffffff; +$tab-font-color: #495057; +$border-color: #dee2e6; +$pills-color: #ed9345; +$radius : .25rem; -div.tabs.test { +div.tabs { + margin: 3em; - font-size: 300%; + nav { + ul.nav-tabs { + + display: flex; + display: -ms-flexbox; + flex-wrap: wrap; + -ms-flex-wrap: wrap; + + list-style: none; + padding-left: 0; + margin-bottom: 0; + + li.nav-item { + margin-bottom: -1px; + + a.nav-link { + display: block; + padding: .5rem 1rem; + } + } + + } + } + + div.tab-content { + + div.tab-pane { + display: none; + + &.fade { + transition: opacity .15s linear; + } + &.active { + display: block; + } + } + } + + &:not(.pills) { + nav { + ul.nav-tabs { + border-bottom: 1px solid $border-color; + + li.nav-item { + + a.nav-link { + border: 1px solid transparent; + border-top-left-radius: $radius; + border-top-right-radius: $radius; + color: $navigation-color; + } + + &.show a.nav-link, + & a.nav-link.active { + + color: $tab-font-color; + background-color: $body-font-color; + border-color: $border-color $border-color $body-font-color; + + } + } + } + } + } + + &.pills { + nav { + ul.nav-tabs { + + border-bottom: 0; + li.nav-item { + + & a.nav-link { + border: 0; + border-radius: $radius; + } + + &.show > a.nav-link, + & a.nav-link.active { + color: $body-font-color; + background-color: $pills-color; + } + } + } + } + } } \ No newline at end of file diff --git a/Resources/views/Test/index.html.twig b/Resources/views/Test/index.html.twig index f1562bfa9..e391dbe56 100644 --- a/Resources/views/Test/index.html.twig +++ b/Resources/views/Test/index.html.twig @@ -7,23 +7,108 @@ {% endblock %} {% block js%} + {% endblock %} {% block content %} +
    + + + +
    +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.

    +
    +
    +

    Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.

    +
    +
    +

    In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.

    +
    +
    +

    Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.

    +
    +
    +

    Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.

    +
    +
    -
    - test
    +
    -

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.

    + -

    Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.

    - -

    In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.

    - -

    Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.

    - -

    Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.

    +
    +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.

    +
    +
    +

    Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.

    +
    +
    +

    In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.

    +
    +
    +

    Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.

    +
    +
    +

    Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.

    +
    +
    +
    {% endblock %} \ No newline at end of file From 01a79f1b7967525c85ee3bc02e804bc4818c45c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 13 Dec 2019 12:39:13 +0100 Subject: [PATCH 17/43] [style] add an extra space around choices expanded widget --- CHANGELOG.md | 1 + Resources/public/sass/modules/_forms.scss | 7 +++++++ Resources/views/Form/fields.html.twig | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 970fa0fde..545f163e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,3 +98,4 @@ Branch CRUD-Init - create an api for rendering entities - css: render the placeholder in expanded choice item as italic (the "no specified" choice") +- css: add an extra space around choices expanded widget diff --git a/Resources/public/sass/modules/_forms.scss b/Resources/public/sass/modules/_forms.scss index f2c533906..b01c802b6 100644 --- a/Resources/public/sass/modules/_forms.scss +++ b/Resources/public/sass/modules/_forms.scss @@ -34,6 +34,8 @@ label { .inline-choice { white-space:nowrap; + display: inline-block; + label { white-space:normal; display: inline; @@ -43,6 +45,11 @@ label { } } +div.choice-widget-expanded { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + textarea, //input, #{$all-text-inputs}, diff --git a/Resources/views/Form/fields.html.twig b/Resources/views/Form/fields.html.twig index 841dd72e2..97e2896dc 100644 --- a/Resources/views/Form/fields.html.twig +++ b/Resources/views/Form/fields.html.twig @@ -52,7 +52,7 @@ {% block choice_widget_expanded %} {% spaceless %} -
    +
    {% for child in form %} {{ form_widget(child) }} From fb9c3cdff35a5abd582fdb120837f0154f15dd29 Mon Sep 17 00:00:00 2001 From: Tchama Date: Fri, 13 Dec 2019 13:03:15 +0100 Subject: [PATCH 18/43] hop --- Resources/public/modules/tabs/tabs.js | 28 ++++++++++++++++++++++++--- Resources/views/Test/index.html.twig | 11 ++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Resources/public/modules/tabs/tabs.js b/Resources/public/modules/tabs/tabs.js index 77de95bff..13957abee 100644 --- a/Resources/public/modules/tabs/tabs.js +++ b/Resources/public/modules/tabs/tabs.js @@ -29,8 +29,11 @@ let countNewActive = function(links, link) /* * Set class active on both new elements: link and content */ -let setNewActive = function(link, contents, rank) +let setNewActive = function(links, contents, rank) { + if (! links[rank-1]) { rank = 1; } + link = links[rank-1]; + link.classList.add('active'); count = 0; @@ -42,6 +45,19 @@ let setNewActive = function(link, contents, rank) }); } +/* + * Set height of content pane + */ +let setPaneHeight = function(contents) +{ + contents.forEach(function(pane) { + + // let computedStyle = getComputedStyle(pane); + // console.log(computedStyle.height); + // comment prendre la hauteur d'une div masquée avec display:none + }); +} + /* * Main function */ @@ -61,15 +77,21 @@ window.addEventListener('load', function() tabPanel.classList.add('pills'); } + if (! unit.startPanel) { + unit.startPanel = 1; + } + + setPaneHeight(contents); + // initial position - setNewActive(links[unit.startPanel-1], contents, unit.startPanel); + setNewActive(links, contents, unit.startPanel); // listen links.forEach(function(link) { link.addEventListener('click', function() { resetActive(links, contents); - setNewActive(link, contents, countNewActive(links, link)); + setNewActive(links, contents, countNewActive(links, link)); }); }); diff --git a/Resources/views/Test/index.html.twig b/Resources/views/Test/index.html.twig index e391dbe56..046d8e22e 100644 --- a/Resources/views/Test/index.html.twig +++ b/Resources/views/Test/index.html.twig @@ -12,17 +12,18 @@ let tabParams = [{ id : 'test1', type: 'tab', - startPanel: 5, + startPanel: 2, disposition: 'horizontal', fade: true }, { id : 'test2', type: 'pill', - startPanel: 3, + startPanel: 5, disposition: 'vertical', fade: false - }]; + } + ]; @@ -46,7 +47,7 @@ Link 4 @@ -87,7 +88,7 @@ Link 4 From 03c75b811e4854a5f4f8714e5ba8bc99d6b22da8 Mon Sep 17 00:00:00 2001 From: Tchama Date: Fri, 13 Dec 2019 14:51:46 +0100 Subject: [PATCH 19/43] tabs feature, data sent from controller. --- Controller/DefaultController.php | 54 +++++++++++++- Resources/public/modules/tabs/tabs.js | 28 ++++++-- Resources/views/Test/index.html.twig | 100 ++++++-------------------- 3 files changed, 97 insertions(+), 85 deletions(-) diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php index dc6bc222a..a1d1baa6c 100644 --- a/Controller/DefaultController.php +++ b/Controller/DefaultController.php @@ -23,6 +23,58 @@ class DefaultController extends Controller public function testAction() { - return $this->render('ChillMainBundle:Test:index.html.twig', []); + return $this->render('ChillMainBundle:Test:index.html.twig', [ + 'tabs' => [ + "test1" => [ + [ + 'name' => "Link 1", + 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", + ], + [ + 'name' => "Link 2", + 'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.", + ], + [ + 'name' => "Link 3", + 'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.", + ], + [ + 'name' => "Link 4", + 'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.", + ], + [ + 'name' => "Link 5", + 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", + ] + ], + "test2" => [ + [ + 'name' => "Link 1", + 'link' => "http://localhost", + 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", + ], + [ + 'name' => "Link 2", + //'link' => "http://localhost", + 'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.", + ], + [ + 'name' => "Link 3", + //'link' => "http://localhost", + 'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.", + ], + [ + 'name' => "Link 4", + 'link' => "http://localhost", + //'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.", + ], + [ + 'name' => "Link 5", + //'link' => "http://localhost", + 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", + ] + ], + ] + ]); } } \ No newline at end of file diff --git a/Resources/public/modules/tabs/tabs.js b/Resources/public/modules/tabs/tabs.js index 13957abee..f03cdcef4 100644 --- a/Resources/public/modules/tabs/tabs.js +++ b/Resources/public/modules/tabs/tabs.js @@ -58,6 +58,20 @@ let setPaneHeight = function(contents) }); } +/* + * Check if links are defined in controller + * If true, disable javascript listener + */ +let isLinkRef = function(link) { + + if (link.getAttribute('href') == "#") { + + return false; + } + return true; + +} + /* * Main function */ @@ -82,17 +96,19 @@ window.addEventListener('load', function() } setPaneHeight(contents); - + // initial position setNewActive(links, contents, unit.startPanel); // listen links.forEach(function(link) { - link.addEventListener('click', function() - { - resetActive(links, contents); - setNewActive(links, contents, countNewActive(links, link)); - }); + if (isLinkRef(link) == false) { + link.addEventListener('click', function() + { + resetActive(links, contents); + setNewActive(links, contents, countNewActive(links, link)); + }); + } }); }); diff --git a/Resources/views/Test/index.html.twig b/Resources/views/Test/index.html.twig index 046d8e22e..93a14d59a 100644 --- a/Resources/views/Test/index.html.twig +++ b/Resources/views/Test/index.html.twig @@ -30,86 +30,30 @@ {% endblock %} {% block content %} -
    - - -
    -
    -

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.

    -
    -
    -

    Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.

    -
    -
    -

    In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.

    -
    -
    -

    Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.

    -
    -
    -

    Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.

    + {% for key,tabsPanel in tabs %} +
    + +
    + {% for tab in tabsPanel %} +
    + {% if tab.content is defined %} +

    {{ tab.content }}

    + {% endif %} +
    + {% endfor %}
    + {% endfor %}
    -
    - - - -
    -
    -

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.

    -
    -
    -

    Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.

    -
    -
    -

    In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.

    -
    -
    -

    Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.

    -
    -
    -

    Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.

    -
    -
    - -
    -{% endblock %} \ No newline at end of file +{% endblock %} From 054adcbc43f74da60aec4e2c2d2540e4912b05ae Mon Sep 17 00:00:00 2001 From: Tchama Date: Fri, 13 Dec 2019 18:08:57 +0100 Subject: [PATCH 20/43] tabs feature, adding macro and documentation --- Controller/DefaultController.php | 11 ++- Resources/public/modules/tabs/tabs.js | 61 +++++++------ Resources/views/Tabs/README.md | 126 ++++++++++++++++++++++++++ Resources/views/Tabs/index.html.twig | 43 +++++++++ Resources/views/Tabs/macro.html.twig | 36 ++++++++ Resources/views/Test/index.html.twig | 59 ------------ 6 files changed, 243 insertions(+), 93 deletions(-) create mode 100644 Resources/views/Tabs/README.md create mode 100644 Resources/views/Tabs/index.html.twig create mode 100644 Resources/views/Tabs/macro.html.twig delete mode 100644 Resources/views/Test/index.html.twig diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php index a1d1baa6c..5909f5f1c 100644 --- a/Controller/DefaultController.php +++ b/Controller/DefaultController.php @@ -23,9 +23,9 @@ class DefaultController extends Controller public function testAction() { - return $this->render('ChillMainBundle:Test:index.html.twig', [ + return $this->render('ChillMainBundle:Tabs:index.html.twig', [ 'tabs' => [ - "test1" => [ + 'test1' => [ [ 'name' => "Link 1", 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", @@ -47,7 +47,7 @@ class DefaultController extends Controller 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", ] ], - "test2" => [ + 'test2' => [ [ 'name' => "Link 1", 'link' => "http://localhost", @@ -73,8 +73,9 @@ class DefaultController extends Controller //'link' => "http://localhost", 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", ] - ], + ] ] ]); } -} \ No newline at end of file + +} diff --git a/Resources/public/modules/tabs/tabs.js b/Resources/public/modules/tabs/tabs.js index f03cdcef4..dffea48aa 100644 --- a/Resources/public/modules/tabs/tabs.js +++ b/Resources/public/modules/tabs/tabs.js @@ -78,38 +78,41 @@ let isLinkRef = function(link) { window.addEventListener('load', function() { tabParams.forEach(function(unit) { + + let tabPanel = document.querySelector('#'+ unit.id ); + if (tabPanel) { - let - tabPanel = document.querySelector('#'+ unit.id ), - nav = tabPanel.querySelector('nav'), - tabs = nav.querySelectorAll('ul.nav-tabs li.nav-item'), - links = nav.querySelectorAll('ul.nav-tabs li.nav-item a.nav-link'), - contents = tabPanel.querySelectorAll('div.tab-content div.tab-pane') - ; + let + nav = tabPanel.querySelector('nav'), + tabs = nav.querySelectorAll('ul.nav-tabs li.nav-item'), + links = nav.querySelectorAll('ul.nav-tabs li.nav-item a.nav-link'), + contents = tabPanel.querySelectorAll('div.tab-content div.tab-pane') + ; - if (unit.type == 'pill') { - tabPanel.classList.add('pills'); - } - - if (! unit.startPanel) { - unit.startPanel = 1; - } - - setPaneHeight(contents); - - // initial position - setNewActive(links, contents, unit.startPanel); - - // listen - links.forEach(function(link) { - if (isLinkRef(link) == false) { - link.addEventListener('click', function() - { - resetActive(links, contents); - setNewActive(links, contents, countNewActive(links, link)); - }); + if (unit.type == 'pill') { + tabPanel.classList.add('pills'); } - }); + if (! unit.initPane) { + unit.initPane = 1; + } + + setPaneHeight(contents); + + // initial position + setNewActive(links, contents, unit.initPane); + + // listen + links.forEach(function(link) { + if (isLinkRef(link) == false) { + link.addEventListener('click', function() + { + resetActive(links, contents); + setNewActive(links, contents, countNewActive(links, link)); + }); + } + }); + + } }); }); \ No newline at end of file diff --git a/Resources/views/Tabs/README.md b/Resources/views/Tabs/README.md new file mode 100644 index 000000000..aa824fc1f --- /dev/null +++ b/Resources/views/Tabs/README.md @@ -0,0 +1,126 @@ +# Fonctionnalité Tabs sur Chill-Main + +Version 0.2 + +(to be translated) + +## Description générale + +**Tabs** est pensé comme une fonctionnalité paramétrique, qui permet de déployer facilement un ou plusieurs groupes d'onglets, avec des options qui permettent de varier son affichage et son comportement. + +Le comportement naturel, ce sont des panneaux qui sont affichés ou cachés en javascript, lorqu'on clique sur l'onglet relatif. + + + +## Controller +``` +// in myController.php + +public function myPageAction() +{ + return $this->render('mytemplate.html.twig', [ + 'tabs' => [ + 'myFirstPanel' => [ + [ 'name' => "Link 1", + 'content' => "content text" + ], + [ 'name' => "Link 2", + 'content' => "content text for link 2" + ] + ], + 'mySecondPanel' => [ + [ 'name' => "Link 1", + 'content' => "content text" + ], + [ 'name' => "Link 2", + 'link' => "http://my.url" + ] + ] + ], + // ... + ]); +} +``` +#### Description + +* On peut définir plusieurs panneaux d'onglets qui s'afficheront dans une même page ; +* Voici la description du tableau passé au template depuis le controller : + + * Au départ on rassemble tous les panneaux d'onglets dans une seule clé, nommée ici `tabs` (la variable est utilisée dans le template, mais son nom peut changer); + * Au niveau suivant, chaque clé correspond à l'identifiant(id) d'un panneau d'onglet ; + * Au niveau suivant, chaque onglet est défini par un tableau, qui comprends les clés suivantes : + + * la clé `name`, le titre de l'onglet ; + * la clé `content`, le contenu du panneau relatif à l'onglet. A ce stade, si `content` contient un tableau, il vous faudra adapter la boucle twig de la macro (voir ci-dessous) ; + * une clé `link` (facultative) + +* Lorsque la clé `link` est définie pour un onglet, le comportement est différent : le lien est suivi et on quitte la page. Un panneau d'onglets peut être mixte, avec certains onglets qui sont en réalité des liens. + + +## Template + +``` +{# in mytemplate.html.twig #} + +{% block css%} + +{% endblock %} + +{% block js%} + + +{% endblock %} + +{% import '@ChillMain/Tabs/macro.html.twig' as tabsPanel %} + +{% block content %} + + {# Display one tabsPanel #} + {{ tabsPanel.displayOne(tabs, 'myFirstPanel') }} + + {# Display all tabPanels #} + {{ tabsPanel.display(tabs) }} + +{% endblock %} +``` + +* Il faut commencer par charger la feuille de style `tabs.css` et le script `tabs.js` à l'intérieur des blocs css et js ; +* Avant d'appeler `tabs.js`, on définit un tableau d'options pour le script : + + * **id**: string, la clé identifiant du panneau d'onglet (obligatoire) + * **type**: [tab|pill] affecte l'affichage (default: tab) + * **initPane**: entier, numéro de l'onglet à afficher au chargement de la page (default: 1) + * [prévu] **disposition**: [horizontal|vertical] affecte l'affichage (default: horizontal) + * [prévu] **fade**: boolean, transition (default: false) + +* Ensuite on peut importer la macro `@ChillMain/Tabs/macro.html.twig` qui permet d'afficher les panneaux d'onglets, soit un par un, soit tous les panneaux ; +* Ou on peut s'inspirer des boucles twig de la macro pour en écrire de nouvelles personnalisées ; + + +## Limitations +* On ne peut pas afficher plusieurs fois le même panneau dans la même page + +## Aller plus loin +* fixer les options disposition et fade +* js, calculer puis fixer la hauteur du panneau suivant le plus long +* controller, avec link, on clique il charge une nouvelle page, avec le même panneau d'onglet ?! +* insérer le initPane dans les urls en paramètres GET &initPane=xx +* quand tab.content n'est pas un string : un bloc de code html, un tableau.. \ No newline at end of file diff --git a/Resources/views/Tabs/index.html.twig b/Resources/views/Tabs/index.html.twig new file mode 100644 index 000000000..3aaadff56 --- /dev/null +++ b/Resources/views/Tabs/index.html.twig @@ -0,0 +1,43 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %}Page de test{% endblock %} + +{% block css%} + +{% endblock %} + +{% block js%} + + +{% endblock %} + +{% import '@ChillMain/Tabs/macro.html.twig' as tabsPanel %} + +{% block content %} + + {# Display one tabsPanel + {{ tabsPanel.displayOne(tabs, 'test1') }} + #} + + {# Display all tabPanels #} + {{ tabsPanel.display(tabs) }} + +{% endblock %} diff --git a/Resources/views/Tabs/macro.html.twig b/Resources/views/Tabs/macro.html.twig new file mode 100644 index 000000000..c5d06935a --- /dev/null +++ b/Resources/views/Tabs/macro.html.twig @@ -0,0 +1,36 @@ +{# + Display only one panel +#} +{% macro displayOne(tabs, panel) %} +
    + +
    + {% for tab in tabs[panel] %} +
    + {% if tab.content is defined %} +

    {{ tab.content }}

    + {% endif %} +
    + {% endfor %} +
    +
    +{% endmacro %} + +{# + Display all panels +#} +{% macro display(tabs) %} + {% import _self as tabsPanel %} + {% for panel,array in tabs %} + {{ tabsPanel.displayOne(tabs, panel) }} + {% endfor %} +{% endmacro %} \ No newline at end of file diff --git a/Resources/views/Test/index.html.twig b/Resources/views/Test/index.html.twig deleted file mode 100644 index 93a14d59a..000000000 --- a/Resources/views/Test/index.html.twig +++ /dev/null @@ -1,59 +0,0 @@ -{% extends '@ChillMain/layout.html.twig' %} - -{% block title %}Page de test{% endblock %} - -{% block css%} - -{% endblock %} - -{% block js%} - - -{% endblock %} - -{% block content %} - - {% for key,tabsPanel in tabs %} -
    - -
    - {% for tab in tabsPanel %} -
    - {% if tab.content is defined %} -

    {{ tab.content }}

    - {% endif %} -
    - {% endfor %} -
    -
    - {% endfor %} - -
    -{% endblock %} From 0ade3b9f4e279c65c177c0c90f6c1d7811af03b4 Mon Sep 17 00:00:00 2001 From: Tchama Date: Fri, 13 Dec 2019 18:22:39 +0100 Subject: [PATCH 21/43] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 545f163e1..e405a2b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,3 +99,4 @@ Branch CRUD-Init - create an api for rendering entities - css: render the placeholder in expanded choice item as italic (the "no specified" choice") - css: add an extra space around choices expanded widget +- add Tabs parametric feature to easily render tabs panels From 323cc860d7deb34c1bc1a7ff67864fcb1b4ad8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 20 Dec 2019 11:48:04 +0100 Subject: [PATCH 22/43] [css] add a margin on the button "delete entry" in collection --- CHANGELOG.md | 1 + Resources/public/js/collection/collection.scss | 4 ++++ Resources/public/js/collection/collections.js | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e405a2b50..184345281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,3 +100,4 @@ Branch CRUD-Init - css: render the placeholder in expanded choice item as italic (the "no specified" choice") - css: add an extra space around choices expanded widget - add Tabs parametric feature to easily render tabs panels +- css: add a margin on the button "delete entry" in collection diff --git a/Resources/public/js/collection/collection.scss b/Resources/public/js/collection/collection.scss index 13fc4a528..ba2f9ce9e 100644 --- a/Resources/public/js/collection/collection.scss +++ b/Resources/public/js/collection/collection.scss @@ -13,6 +13,10 @@ div.chill-collection { li.chill-collection__list__entry:nth-last-child(1n+2) { margin-bottom: 1rem; } + + button.chill-collection__list__remove-entry { + margin-left: 0.5rem; + } } button.chill-collection__button--add { diff --git a/Resources/public/js/collection/collections.js b/Resources/public/js/collection/collections.js index 60b34fb17..e8e697058 100644 --- a/Resources/public/js/collection/collections.js +++ b/Resources/public/js/collection/collections.js @@ -76,7 +76,7 @@ var initializeRemove = function(collection, entry) { return; } - button.classList.add('sc-button', 'bt-delete'); + button.classList.add('sc-button', 'bt-delete', 'chill-collection__list__remove-entry'); button.textContent = content; button.addEventListener('click', function(e) { From 1f1c9fb726ed6b397f404d38f8aca36629a01efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 20 Dec 2019 11:48:32 +0100 Subject: [PATCH 23/43] remove dump --- CRUD/Resolver/Resolver.php | 1 - 1 file changed, 1 deletion(-) diff --git a/CRUD/Resolver/Resolver.php b/CRUD/Resolver/Resolver.php index 2475efd42..66ad75e82 100644 --- a/CRUD/Resolver/Resolver.php +++ b/CRUD/Resolver/Resolver.php @@ -100,7 +100,6 @@ class Resolver switch ($key) { case self::ROLE: - dump($config); return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action); } } From 6109520c95ad6c2567a962ed04dc92cf0d2585bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Jan 2020 22:45:17 +0100 Subject: [PATCH 24/43] add block for bottom action buttons --- Resources/views/CRUD/_edit_content.html.twig | 2 ++ Resources/views/CRUD/_view_content.html.twig | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Resources/views/CRUD/_edit_content.html.twig b/Resources/views/CRUD/_edit_content.html.twig index 0ede64748..ce8aa87c4 100644 --- a/Resources/views/CRUD/_edit_content.html.twig +++ b/Resources/views/CRUD/_edit_content.html.twig @@ -21,6 +21,7 @@ {% endblock %} + {% block content_form_actions_before %}{% endblock %} {% block content_form_actions_view %} {% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %}
  • @@ -44,6 +45,7 @@
  • {% endblock %} + {% block content_form_actions_after %}{% endblock %} {% endblock %} diff --git a/Resources/views/CRUD/_view_content.html.twig b/Resources/views/CRUD/_view_content.html.twig index 39cf85359..53d205cb0 100644 --- a/Resources/views/CRUD/_view_content.html.twig +++ b/Resources/views/CRUD/_view_content.html.twig @@ -21,6 +21,7 @@ {% endblock %} + {% block content_view_actions_before %}{% endblock %} {% block content_view_actions_duplicate_link %} {% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
  • @@ -39,6 +40,7 @@
  • {% endif %} {% endblock content_view_actions_edit_link %} + {% block content_view_actions_after %}{% endblock %} {% endblock crud_content_view_actions %} From ce365b2c41a563ea5dcabad14fb45f16f35bfd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Jan 2020 22:45:30 +0100 Subject: [PATCH 25/43] add support for re-use of same controller in CRUD --- CRUD/Routing/CRUDRoutesLoader.php | 3 +-- DependencyInjection/ChillMainExtension.php | 8 +++++--- DependencyInjection/Configuration.php | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CRUD/Routing/CRUDRoutesLoader.php b/CRUD/Routing/CRUDRoutesLoader.php index 6ead91a5e..aef4aec69 100644 --- a/CRUD/Routing/CRUDRoutesLoader.php +++ b/CRUD/Routing/CRUDRoutesLoader.php @@ -62,10 +62,9 @@ class CRUDRoutesLoader protected function loadConfig($config): RouteCollection { $collection = new RouteCollection(); - foreach ($config['actions'] as $name => $action) { $defaults = [ - '_controller' => $action['controller'] ?? $config['controller'].'::'.$name + '_controller' => 'cscrud_'.$config['name'].'_controller'.':'.($action['controller_action'] ?? $name) ]; if ($name === 'index') { diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php index af83e4c12..3a0038c17 100644 --- a/DependencyInjection/ChillMainExtension.php +++ b/DependencyInjection/ChillMainExtension.php @@ -228,6 +228,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, foreach ($config as $crudEntry) { $controller = $crudEntry['controller']; + $controllerServiceName = 'cscrud_'.$crudEntry['name'].'_controller'; $name = $crudEntry['name']; // check for existing crud names @@ -235,15 +236,16 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name)); } - if (!$container->has($controller)) { + if (!$container->has($controllerServiceName)) { $controllerDefinition = new Definition($controller); $controllerDefinition->addTag('controller.service_arguments'); $controllerDefinition->setAutoconfigured(true); - $container->setDefinition($controller, $controllerDefinition); + $controllerDefinition->setClass($crudEntry['controller']); + $container->setDefinition($controllerServiceName, $controllerDefinition); } $container->setParameter('chill_main_crud_config_'.$name, $crudEntry); - $container->getDefinition($controller) + $container->getDefinition($controllerServiceName) ->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']); } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index f718ecdd9..de22136a3 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -134,10 +134,10 @@ class Configuration implements ConfigurationInterface ->useAttributeAsKey('name') ->arrayPrototype() ->children() - ->scalarNode('controller') + ->scalarNode('controller_action') ->defaultNull() ->info('the method name to call in the route. Will be set to the action name if left empty.') - ->example("'MyBundle\Controller\MyCrudController::action'") + ->example("'action'") ->end() ->scalarNode('path') ->defaultNull() From 99632344a687106b0efeae33262391b36ee24513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 24 Jan 2020 13:36:23 +0100 Subject: [PATCH 26/43] allow to launch show / hide manually Useful when show/hide occurs in collection --- CHANGELOG.md | 1 + .../public/modules/show_hide/show_hide.js | 25 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 184345281..b98eaa500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,3 +101,4 @@ Branch CRUD-Init - css: add an extra space around choices expanded widget - add Tabs parametric feature to easily render tabs panels - css: add a margin on the button "delete entry" in collection +- module `show_hide`: add the possibility to launch a show hide manually and not on page loading. Useful when show/hide occurs in collection. diff --git a/Resources/public/modules/show_hide/show_hide.js b/Resources/public/modules/show_hide/show_hide.js index a634df212..f22064433 100644 --- a/Resources/public/modules/show_hide/show_hide.js +++ b/Resources/public/modules/show_hide/show_hide.js @@ -23,9 +23,14 @@ var ShowHide = function(options) { container = typeof options.container[Symbol.iterator] === "function" ? options.container : [ options.container ], is_shown = true, event_name = 'event_name' in options ? options.event_name : 'change', - container_content = []; - - window.addEventListener('load', function(event) { + container_content = [], + debug = 'debug' in options ? options.debug : false, + load_event = 'load_event' in options ? options.load_event : 'load'; + + var bootstrap = function(event) { + if (debug) { + console.log('debug is activated on this show-hide', this); + } // keep the content in memory for (let c of container.values()) { let contents = []; @@ -39,6 +44,9 @@ var ShowHide = function(options) { for (let f of froms.values()) { let inputs = f.querySelectorAll('input'); for (let input of inputs.values()) { + if (debug) { + console.log('attaching event to input', input); + } input.addEventListener(event_name, function(e) { onChange(e); }); @@ -46,8 +54,8 @@ var ShowHide = function(options) { } // first launch of the show/hide - onChange(event); - }); + onChange(event); + }; var onChange = function (event) { @@ -77,6 +85,13 @@ var ShowHide = function(options) { } }; + + + if (load_event !== null) { + window.addEventListener('load', bootstrap); + } else { + bootstrap(null); + } }; export {ShowHide}; From d46ee72fb18ccccf2bde4571d82cc20b563bdbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 24 Jan 2020 15:42:50 +0100 Subject: [PATCH 27/43] [show/hide] add events to module --- CHANGELOG.md | 1 + .../public/modules/show_hide/show_hide.js | 59 ++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b98eaa500..b06b6469d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,3 +102,4 @@ Branch CRUD-Init - add Tabs parametric feature to easily render tabs panels - css: add a margin on the button "delete entry" in collection - module `show_hide`: add the possibility to launch a show hide manually and not on page loading. Useful when show/hide occurs in collection. +- module `show_hide`: add events to module diff --git a/Resources/public/modules/show_hide/show_hide.js b/Resources/public/modules/show_hide/show_hide.js index f22064433..5f65e2b18 100644 --- a/Resources/public/modules/show_hide/show_hide.js +++ b/Resources/public/modules/show_hide/show_hide.js @@ -25,7 +25,8 @@ var ShowHide = function(options) { event_name = 'event_name' in options ? options.event_name : 'change', container_content = [], debug = 'debug' in options ? options.debug : false, - load_event = 'load_event' in options ? options.load_event : 'load'; + load_event = 'load_event' in options ? options.load_event : 'load', + id = 'uid' in options ? options.id : Math.random(); var bootstrap = function(event) { if (debug) { @@ -59,26 +60,19 @@ var ShowHide = function(options) { var onChange = function (event) { - var result = test(froms, event); + var result = test(froms, event), me; if (result === true) { if (is_shown === false) { - for (let i of container_content.keys()) { - var contents = container_content[i]; - for (let el of contents.values()) { - container[i].appendChild(el); - } - } - is_shown = true; + forceShow(); + me = new CustomEvent('show-hide-show', { detail: { id: id, container: container, froms: froms } }); + window.dispatchEvent(me); } } else if (result === false) { if (is_shown) { - for (let contents of container_content.values()) { - for (let el of contents.values()) { - el.remove(); - } - } - is_shown = false; + forceHide(); + me = new CustomEvent('show-hide-hide', { detail: { id: id, container: container, froms: froms } }); + window.dispatchEvent(me); } } else { throw "the result of test is not a boolean"; @@ -86,12 +80,47 @@ var ShowHide = function(options) { }; + var forceHide = function() { + if (debug) { + console.log('force hide'); + } + for (let contents of container_content.values()) { + for (let el of contents.values()) { + el.remove(); + } + } + is_shown = false; + }; + + var forceShow = function() { + if (debug) { + console.log('show'); + } + for (let i of container_content.keys()) { + var contents = container_content[i]; + for (let el of contents.values()) { + container[i].appendChild(el); + } + } + is_shown = true; + }; + + var forceCompute = function(event) { + onChange(event); + }; + if (load_event !== null) { window.addEventListener('load', bootstrap); } else { bootstrap(null); } + + return { + forceHide: forceHide, + forceShow: forceShow, + forceCompute: forceCompute, + }; }; export {ShowHide}; From e348616d00aeb71ca1613bf49df8c277d632bd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 10 Feb 2020 13:03:57 +0100 Subject: [PATCH 28/43] [phonenumber validation] allow to validate against landline and mobile phonenumbers --- CHANGELOG.md | 2 ++ Phonenumber/PhonenumberHelper.php | 18 ++++++++++++++++++ Resources/translations/validators.fr.yml | 1 + .../Constraint/PhonenumberConstraint.php | 6 ++++-- Validation/Validator/ValidPhonenumber.php | 6 +++++- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b06b6469d..03d8b2d6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,3 +103,5 @@ Branch CRUD-Init - css: add a margin on the button "delete entry" in collection - module `show_hide`: add the possibility to launch a show hide manually and not on page loading. Useful when show/hide occurs in collection. - module `show_hide`: add events to module +- [phonenumber validation] allow to validate against mobile **or** landline/voip phonenumbers; + diff --git a/Phonenumber/PhonenumberHelper.php b/Phonenumber/PhonenumberHelper.php index 4e06472d3..5bb362e15 100644 --- a/Phonenumber/PhonenumberHelper.php +++ b/Phonenumber/PhonenumberHelper.php @@ -122,6 +122,24 @@ class PhonenumberHelper return \in_array($validation, [ 'landline', 'voip' ]); } + /** + * Return true if the phonenumber is a landline or voip phone. Return always false + * if the validation is not configured. + * + * @param string $phonenumber + * @return bool + */ + public function isValidPhonenumberAny($phonenumber) : bool + { + $validation = $this->performTwilioLookup($phonenumber); + + if (NULL === $validation) { + return false; + } + + return \in_array($validation, [ 'landline', 'voip', 'mobile' ]); + } + public function format($phonenumber) { return $this->performTwilioFormat($phonenumber); diff --git a/Resources/translations/validators.fr.yml b/Resources/translations/validators.fr.yml index ab3596c79..766bee9da 100644 --- a/Resources/translations/validators.fr.yml +++ b/Resources/translations/validators.fr.yml @@ -17,3 +17,4 @@ This username or email does not exists: Cet utilisateur ou email n'est pas prés #phonenumber This is not a landline phonenumber: Ce numéro n'est pas une ligne fixe valide This is not a mobile phonenumber: Ce numéro n'est pas un numéro de portable valide +This is not a valid phonenumber: Ce numéro de téléphone n'est pas valide diff --git a/Validation/Constraint/PhonenumberConstraint.php b/Validation/Constraint/PhonenumberConstraint.php index 9e322285d..cf4479456 100644 --- a/Validation/Constraint/PhonenumberConstraint.php +++ b/Validation/Constraint/PhonenumberConstraint.php @@ -21,7 +21,7 @@ use Symfony\Component\Validator\Constraint; /** * - * + * @Annotation */ class PhonenumberConstraint extends Constraint { @@ -29,10 +29,12 @@ class PhonenumberConstraint extends Constraint public $notLandlineMessage = "This is not a landline phonenumber"; + public $notValidMessage = "This is not a valid phonenumber"; + /** * The type of phone: landline (not able to receive sms) or mobile (can receive sms) * - * @var string 'landline' or 'mobile' + * @var string 'landline', 'mobile' or 'any' */ public $type = null; diff --git a/Validation/Validator/ValidPhonenumber.php b/Validation/Validator/ValidPhonenumber.php index 971dedc25..127bfad33 100644 --- a/Validation/Validator/ValidPhonenumber.php +++ b/Validation/Validator/ValidPhonenumber.php @@ -70,10 +70,14 @@ class ValidPhonenumber extends ConstraintValidator $isValid = $this->phonenumberHelper->isValidPhonenumberMobile($value); $message = $constraint->notMobileMessage; break; + case 'any': + $isValid = $this->phonenumberHelper->isValidPhonenumberAny($value); + $message = $constraint->notValidMessage; + break; default: throw new \LogicException(sprintf("This type '%s' is not implemented. " - . "Possible values are 'mobile', 'landline'"), $constraint->type); + . "Possible values are 'mobile', 'landline' or 'any'", $constraint->type)); } if (FALSE === $isValid) { From 7a952d6f8802a19392751d7441e9c0c4f9277090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 10 Mar 2020 09:48:59 +0100 Subject: [PATCH 29/43] [phonenumber validation & format] format and validation does not make the app fail when network is not available --- CHANGELOG.md | 2 +- Phonenumber/PhonenumberHelper.php | 17 +++++++++++++++++ Phonenumber/Templating.php | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d8b2d6e..209261093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,4 +104,4 @@ Branch CRUD-Init - module `show_hide`: add the possibility to launch a show hide manually and not on page loading. Useful when show/hide occurs in collection. - module `show_hide`: add events to module - [phonenumber validation] allow to validate against mobile **or** landline/voip phonenumbers; - +- [phonenumber validation & format] format and validation does not make the app fail when network is not available; diff --git a/Phonenumber/PhonenumberHelper.php b/Phonenumber/PhonenumberHelper.php index 5bb362e15..8b3df2894 100644 --- a/Phonenumber/PhonenumberHelper.php +++ b/Phonenumber/PhonenumberHelper.php @@ -20,6 +20,7 @@ namespace Chill\MainBundle\Phonenumber; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ServerException; +use GuzzleHttp\Exception\ConnectException; use Psr\Log\LoggerInterface; use Psr\Cache\CacheItemPoolInterface; @@ -182,6 +183,14 @@ class PhonenumberHelper "phonenumber" => $phonenumber ]); + return null; + } catch (ConnectException $e) { + $this->logger->error("[phonenumber helper] Could not format number " + . "due to connect error", [ + "message" => $e->getMessage(), + "phonenumber" => $phonenumber + ]); + return null; } @@ -230,6 +239,14 @@ class PhonenumberHelper "phonenumber" => $phonenumber ]); + return null; + } catch (ConnectException $e) { + $this->logger->error("[phonenumber helper] Could not format number " + . "due to connect error", [ + "message" => $e->getMessage(), + "phonenumber" => $phonenumber + ]); + return null; } diff --git a/Phonenumber/Templating.php b/Phonenumber/Templating.php index daaee8956..45bfceae0 100644 --- a/Phonenumber/Templating.php +++ b/Phonenumber/Templating.php @@ -47,6 +47,6 @@ class Templating extends AbstractExtension public function formatPhonenumber($phonenumber) { - return $this->phonenumberHelper->format($phonenumber); + return $this->phonenumberHelper->format($phonenumber) ?? $phonenumber; } } From ba742dd7bec30a69229be500de1763c88a84f61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 12 Mar 2020 12:13:41 +0100 Subject: [PATCH 30/43] create index action for CRUD --- CRUD/Controller/CRUDController.php | 92 ++++++++++++--------- Resources/translations/messages.fr.yml | 3 +- Resources/views/CRUD/_index.html.twig | 48 +++++++++++ Resources/views/CRUD/_new_content.html.twig | 2 +- Resources/views/CRUD/_new_title.html.twig | 2 +- Resources/views/CRUD/index.html.twig | 39 +-------- 6 files changed, 109 insertions(+), 77 deletions(-) create mode 100644 Resources/views/CRUD/_index.html.twig diff --git a/CRUD/Controller/CRUDController.php b/CRUD/Controller/CRUDController.php index 70f6e9a75..7a9c1e76b 100644 --- a/CRUD/Controller/CRUDController.php +++ b/CRUD/Controller/CRUDController.php @@ -32,6 +32,7 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Chill\MainBundle\CRUD\Resolver\Resolver; +use Chill\MainBundle\Pagination\PaginatorInterface; /** * @@ -39,12 +40,6 @@ use Chill\MainBundle\CRUD\Resolver\Resolver; */ class CRUDController extends AbstractController { - /** - * - * @var PaginatorFactory - */ - protected $paginatorFactory; - /** * The crud configuration * @@ -59,13 +54,10 @@ class CRUDController extends AbstractController $this->crudConfig = $config; } - protected function getDefaultOrdering(): array - { - return $this->orderingOptions(); - } - protected function processTemplateParameters($action): array { + throw new Exception('is this method used ?'); + $configured = $this->getTemplateParameters($action); switch($action) { @@ -89,43 +81,57 @@ class CRUDController extends AbstractController return $result; } - protected function orderQuery(QueryBuilder $query, Request $request): QueryBuilder + protected function orderQuery($action, QueryBuilder $query, Request $request, PaginatorInterface $paginator): QueryBuilder { - $defaultOrdering = $this->getDefaultOrdering(); - - foreach ($defaultOrdering as $sort => $order) { - $query->addOrderBy('e.'.$sort, $order); - } - return $query; } public function index(Request $request) { - $totalItems = $this->getDoctrine()->getManager() - ->createQuery("SELECT COUNT(e) FROM ".$this->getEntity()." e") - ->getSingleScalarResult() - ; - - $query = $this->getDoctrine()->getManager() - ->createQueryBuilder() - ->select('e') - ->from($this->getEntity(), 'e'); - - $this->orderQuery($query, $request); - - $paginator = $this->paginatorFactory->create($totalItems); - - $query->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) - ->setMaxResults($paginator->getItemsPerPage()) - ; + return $this->indexEntityAction('index', $request); + } + + protected function indexEntityAction($action, Request $request) + { + $totalItems = $this->countEntities($action, $request); + $paginator = $this->getPaginatorFactory()->create($totalItems); + $query = $this->queryEntities($action, $request, $paginator); $entities = $query->getQuery()->getResult(); - return $this->render($this->getTemplate('index'), \array_merge([ - 'entities' => $entities, - ], $this->processTemplateParameters('index')) - ); + $defaultTemplateParameters = [ + 'entities' => $entities, + 'crud_name' => $this->getCrudName(), + 'paginator' => $paginator + ]; + + return $this->render( + $this->getTemplateFor($action, $entities, $request), + $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) + ); + } + + protected function queryEntities($action, Request $request, PaginatorInterface $paginator) + { + $query = $this->getDoctrine()->getManager() + ->createQueryBuilder() + ->select('e') + ->from($this->getEntityClass(), 'e') + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + ->setMaxResults($paginator->getItemsPerPage()) + ; + + $this->orderQuery($action, $query, $request, $paginator); + + return $query; + } + + protected function countEntities($action, Request $request) + { + return $this->getDoctrine()->getManager() + ->createQuery("SELECT COUNT(e) FROM ".$this->getEntityClass()." e") + ->getSingleScalarResult() + ; } public function edit(Request $request, $id): Response @@ -393,6 +399,14 @@ class CRUDController extends AbstractController return new $type; } + /** + * + * @param string $action + * @param mixed $entity the entity for the current request, or an array of entities + * @param Request $request + * @return string + * @throws \LogicException + */ protected function getTemplateFor($action, $entity, Request $request) { if ($this->hasCustomTemplate($action, $entity, $request)) { diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 93c2c303d..bcf6ab106 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -232,6 +232,7 @@ Impersonate: Mode fantôme Impersonate mode: Mode fantôme crud: + # general items new: button_action_form: Créer link_edit: Modifier @@ -248,4 +249,4 @@ crud: default: success: Les données ont été enregistrées view: - link_duplicate: Dupliquer \ No newline at end of file + link_duplicate: Dupliquer diff --git a/Resources/views/CRUD/_index.html.twig b/Resources/views/CRUD/_index.html.twig new file mode 100644 index 000000000..4e553d962 --- /dev/null +++ b/Resources/views/CRUD/_index.html.twig @@ -0,0 +1,48 @@ +
    + +{% block index_header %} +

    {{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}

    +{% endblock index_header %} + +{% if entities|length == 0 %} + {% block no_existing_entities %} +

    {{ no_existing_entities_sentences|default('No entities')|trans }}

    + {% endblock %} +{% else %} + {% block table_entities %} + + + + {% block table_entities_thead_tr %} + + {% endblock %} + + + + {% block table_entities_tbody %} + {% for entity in entities %} + + + + {% endfor %} + {% endblock %} + +
    id
    {{ entity.id }}
    + {% endblock %} + +{% endif %} + +
    + {{ chill_pagination(paginator) }} +
    + +{% block list_actions %} + +{% endblock list_actions %} +
    diff --git a/Resources/views/CRUD/_new_content.html.twig b/Resources/views/CRUD/_new_content.html.twig index 3da85d3ad..93609d8d1 100644 --- a/Resources/views/CRUD/_new_content.html.twig +++ b/Resources/views/CRUD/_new_content.html.twig @@ -1,6 +1,6 @@
    {% block crud_content_header %} -

    {{ 'crud.%crud_name%.title_new'|trans({'%crud_name%' : crud_name }) }}

    +

    {{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}

    {% endblock crud_content_header %} {% block crud_content_form %} diff --git a/Resources/views/CRUD/_new_title.html.twig b/Resources/views/CRUD/_new_title.html.twig index 60c581122..ebd2121a6 100644 --- a/Resources/views/CRUD/_new_title.html.twig +++ b/Resources/views/CRUD/_new_title.html.twig @@ -1 +1 @@ -{{ 'crud.%crud_name%.title_new'|trans({'%crud_name%' : crud_name }) }} \ No newline at end of file +{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }} \ No newline at end of file diff --git a/Resources/views/CRUD/index.html.twig b/Resources/views/CRUD/index.html.twig index 83c8683c4..00608131d 100644 --- a/Resources/views/CRUD/index.html.twig +++ b/Resources/views/CRUD/index.html.twig @@ -1,39 +1,8 @@ {% extends '@ChillMain/layout.html.twig' %} +{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %} + {% block content %} -

    {{ title|default('List of %class%')|trans({'%class%': class}) }}

    - - {% if entities|length == 0 %} -

    {{ no_existing_entities_sentences|default('No entities')|trans }}

    - {% else %} - - - - {% for c in columns %} - - {% endfor %} - - - - - {% for entity in entities %} - - {% for c in columns %} - - {% endfor %} - - - {% endfor %} - -
    {{ c|trans }} 
    {{ entity|chill_crud_display(c) }} -
      - {% for action in actions %} -
    • {{ action }}
    • - {% endfor %} -
    -
    - - {% endif %} - - + {% embed '@ChillMain/CRUD/_index.html.twig' %} + {% endembed %} {% endblock content %} \ No newline at end of file From ad3ced9683fb98322382b39e49dd158244284a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 12 Mar 2020 12:14:38 +0100 Subject: [PATCH 31/43] use Menu api for handling menu --- Resources/config/routing.yml | 6 -- Resources/config/services/menu.yml | 6 ++ Resources/views/Menu/adminSection.html.twig | 10 +-- .../MenuBuilder/AdminSectionMenuBuilder.php | 62 +++++++++++++++++++ 4 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 Routing/MenuBuilder/AdminSectionMenuBuilder.php diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index ee3ca9516..983e5237d 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -58,12 +58,6 @@ chill_main_admin_central: chill_main_admin_permissions: path: /{_locale}/admin/permissions defaults: {_controller: ChillMainBundle:Admin:indexPermissions } - options: - menus: - admin_section: - order: 200 - label: Users and permissions - icons: [key] chill_main_search: path: /{_locale}/search.{_format} diff --git a/Resources/config/services/menu.yml b/Resources/config/services/menu.yml index b56c34d4e..ac331bd5a 100644 --- a/Resources/config/services/menu.yml +++ b/Resources/config/services/menu.yml @@ -10,3 +10,9 @@ services: $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' tags: - { name: 'chill.menu_builder' } + + Chill\MainBundle\Routing\MenuBuilder\AdminSectionMenuBuilder: + arguments: + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + tags: + - { name: 'chill.menu_builder' } diff --git a/Resources/views/Menu/adminSection.html.twig b/Resources/views/Menu/adminSection.html.twig index d93d9d931..5434b4be8 100644 --- a/Resources/views/Menu/adminSection.html.twig +++ b/Resources/views/Menu/adminSection.html.twig @@ -21,16 +21,18 @@ Admin Sections
    From bd21145701d0d675d297ce49014a915c617d1e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 21 Apr 2020 13:28:24 +0200 Subject: [PATCH 40/43] [CRUD] fix error when no crud are created --- CHANGELOG.md | 1 + DependencyInjection/ChillMainExtension.php | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5c69523..ac014cf08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,3 +113,4 @@ Master branch - [chill entity render] fix error when fallback to default entity render (usage of `__toString()`) - [CRUD] add step delete - [CRUD] check that action exists before inserting them in edit and view template +- [CRUD] fix error when no crud are created diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php index 3a0038c17..d33ef4eb9 100644 --- a/DependencyInjection/ChillMainExtension.php +++ b/DependencyInjection/ChillMainExtension.php @@ -99,8 +99,6 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, array('homepage' => $config['widgets']['homepage']): array() ); - - $this->configureCruds($container, $config['cruds']); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); @@ -122,7 +120,8 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $loader->load('services/phonenumber.yml'); $loader->load('services/cache.yml'); $loader->load('services/templating.yml'); - $loader->load('services/crud.yml'); + + $this->configureCruds($container, $config['cruds'], $loader); } public function getConfiguration(array $config, ContainerBuilder $container) @@ -208,12 +207,14 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, * @param array $config the config under 'cruds' key * @return null */ - protected function configureCruds(ContainerBuilder $container, $config) + protected function configureCruds(ContainerBuilder $container, $config, Loader\YamlFileLoader $loader) { if (count($config) === 0) { return; } + $loader->load('services/crud.yml'); + $container->setParameter('chill_main_crud_route_loader_config', $config); $definition = new Definition(); From fe4253174d202777d259cbbc499dc08ef2baae3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 21 Apr 2020 13:32:03 +0200 Subject: [PATCH 41/43] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e4038eb..0e358e127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,8 +112,8 @@ Version 1.5.16 - [translation] in french, replace "Modifier" by "Enregistrer" in the edit form - [entity render] do not throw an exception when null element are passed to `chill_entity_render_box` and `chill_entity_render_string` -Master branch -============= +Version 1.5.17 +============== - [chill entity render] fix error when fallback to default entity render (usage of `__toString()`) - [CRUD] add step delete From 83b1b19f40b43457ff2b554681d39fb7a0e3cf57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 21 Apr 2020 14:45:34 +0200 Subject: [PATCH 42/43] move activity.scss to own bundle + create namespace for sass --- CHANGELOG.md | 6 ++++++ Resources/config/services.yml | 13 ------------- Resources/config/services/templating.yml | 17 +++++++++++++++++ Resources/public/sass/_custom.scss | 1 - Resources/public/sass/custom/_activity.scss | 7 ------- Resources/public/sass/custom/mixins/entity.scss | 3 ++- chill.webpack.config.js | 3 ++- 7 files changed, 27 insertions(+), 23 deletions(-) delete mode 100644 Resources/public/sass/custom/_activity.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e358e127..bbe21ac72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,3 +119,9 @@ Version 1.5.17 - [CRUD] add step delete - [CRUD] check that action exists before inserting them in edit and view template - [CRUD] fix error when no crud are created + +Master branch +============= + +- [webpack] add namespace for import sass ; +- [activity] move activity.scss to own bundle ; diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 05807258b..010deb3ca 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -2,19 +2,6 @@ parameters: # cl_chill_main.example.class: Chill\MainBundle\Example services: - - twig_intl: - class: Twig_Extensions_Extension_Intl - tags: - - { name: twig.extension } - - twig_date: - class: Twig_Extensions_Extension_Date - arguments: - - "@translator" - tags: - - { name: twig.extension } - chill.main.helper.translatable_string: class: Chill\MainBundle\Templating\TranslatableStringHelper arguments: diff --git a/Resources/config/services/templating.yml b/Resources/config/services/templating.yml index 8791c10b1..49836e3f9 100644 --- a/Resources/config/services/templating.yml +++ b/Resources/config/services/templating.yml @@ -1,4 +1,21 @@ services: + twig_intl: + class: Twig_Extensions_Extension_Intl + tags: + - { name: twig.extension } + + twig_date: + class: Twig_Extensions_Extension_Date + arguments: + - "@translator" + tags: + - { name: twig.extension } + + twig_text: + class: Twig_Extensions_Extension_Text + tags: + - { name: twig.extension } + Chill\MainBundle\Templating\ChillTwigHelper: tags: - { name: twig.extension } diff --git a/Resources/public/sass/_custom.scss b/Resources/public/sass/_custom.scss index 867d1b071..ee533a7ee 100644 --- a/Resources/public/sass/_custom.scss +++ b/Resources/public/sass/_custom.scss @@ -4,7 +4,6 @@ @import 'custom/fonts'; @import 'custom/timeline'; @import 'custom/mixins/entity'; -@import 'custom/activity'; @import 'custom/report'; @import 'custom/person'; @import 'custom/pagination'; diff --git a/Resources/public/sass/custom/_activity.scss b/Resources/public/sass/custom/_activity.scss deleted file mode 100644 index 604bc3c7a..000000000 --- a/Resources/public/sass/custom/_activity.scss +++ /dev/null @@ -1,7 +0,0 @@ -span.entity.entity-activity.activity-reason { - @include entity($chill-pink, white); -} - -.activity { - color: $chill-green; -} diff --git a/Resources/public/sass/custom/mixins/entity.scss b/Resources/public/sass/custom/mixins/entity.scss index d32570534..28cc74d06 100644 --- a/Resources/public/sass/custom/mixins/entity.scss +++ b/Resources/public/sass/custom/mixins/entity.scss @@ -1,6 +1,6 @@ @mixin entity($background-color, $color: white) { font-variant: small-caps; - display: inline; + display: inline-block; padding: .2em .6em .3em; font-size: 88%; font-weight: bold; @@ -11,5 +11,6 @@ border-radius: .25em; color: $color; background-color: $background-color; + margin: 0.5em; } diff --git a/chill.webpack.config.js b/chill.webpack.config.js index 4baf22fa2..4b80320cc 100644 --- a/chill.webpack.config.js +++ b/chill.webpack.config.js @@ -4,6 +4,7 @@ module.exports = function(encore, entries) { encore.addEntry('login', './vendor/chill-project/main/Resources/public/modules/login_page/index.js'); encore.addEntry('tabs', './vendor/chill-project/main/Resources/public/modules/tabs/index.js'); encore.addAliases({ - ShowHide: __dirname + '/Resources/public/modules/show_hide/' + ShowHide: __dirname + '/Resources/public/modules/show_hide/', + ChillMainSass: __dirname + '/Resources/public/sass' }); }; From 5f282ecedb984ec6baddeda0953b41562fb4a49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 21 Apr 2020 14:47:13 +0200 Subject: [PATCH 43/43] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbe21ac72..648058e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,8 +120,8 @@ Version 1.5.17 - [CRUD] check that action exists before inserting them in edit and view template - [CRUD] fix error when no crud are created -Master branch -============= +Version 1.5.18 +============== - [webpack] add namespace for import sass ; - [activity] move activity.scss to own bundle ;