From fe8ade2ca8082968a51543a59875e9f2b85f1216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Oct 2016 00:27:12 +0200 Subject: [PATCH] add documentation for widget --- source/development/index.rst | 2 +- .../user-interface/delegated-blocks.rst | 98 ----- source/development/user-interface/widgets.rst | 334 ++++++++++++++++++ .../widgets/ChillMainConfiguration.php | 64 ++++ .../widgets/ChillMainExtension.php | 59 ++++ ...ChillPersonAddAPersonListWidgetFactory.php | 69 ++++ .../widgets/ChillPersonAddAPersonWidget.php | 129 +++++++ .../widgets/ChillPersonExtension.php | 51 +++ 8 files changed, 707 insertions(+), 99 deletions(-) delete mode 100644 source/development/user-interface/delegated-blocks.rst create mode 100644 source/development/user-interface/widgets.rst create mode 100644 source/development/user-interface/widgets/ChillMainConfiguration.php create mode 100644 source/development/user-interface/widgets/ChillMainExtension.php create mode 100644 source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php create mode 100644 source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php create mode 100644 source/development/user-interface/widgets/ChillPersonExtension.php diff --git a/source/development/index.rst b/source/development/index.rst index 043b32920..ea9f1fc60 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -38,7 +38,7 @@ Layout and UI Layout / Template usage Classes and mixins - Delegated blocks + Widgets Help, I am lost ! diff --git a/source/development/user-interface/delegated-blocks.rst b/source/development/user-interface/delegated-blocks.rst deleted file mode 100644 index 74dd97dc2..000000000 --- a/source/development/user-interface/delegated-blocks.rst +++ /dev/null @@ -1,98 +0,0 @@ -.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - A copy of the license is included in the section entitled "GNU - Free Documentation License". - -Delegating rendering of block to other bundles -############################################## - -Sometimes, you may want to delegate part of your layout to another bundle(s), which might, or might not, installed. - -Examples : - -- you may want to show task in the top bar, only if the bundle "task" is installed (**Note**: this bundle does not exists... yet !) -- you may want to show the group belonging (see :ref:`group-bundle`) below of the vertical menu, only if the bundle is installed. - -This is possible using `the symfony dispatcher event `_. - -Inserting a delegated block inside a template -============================================== - -Use the twig function :code:`chill_delegated_block`. - -Example : - -.. code-block:: html+jinja - -
{{ chill_delegated_block('my_block', { 'person': person }) }}
- -In this example, the block name is :code:`my_block`, and the context is an array : :code:`{ 'person': person }`. - -The :code:`div` will be filled with the html produced by the bundles which suscribed to the event :code:`chill_block.my_block`. - -Subscribing to a delegated block -================================= - -Create a :code:`Subscriber` or a :code:`Listener` as `described in the Symfony documentation `_. - -You should listen to the event :code:`chill_block.block_name`, where `block_name` is the name of the delegated block. For instance, in the example above, the event will be :code:`chill_block.my_block`. - -The event passed as argument will be an instance of :class:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent`. The context will be available as an array, as described in subscriber example. You may add content to the page using the :method:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent::addContent` method. - -.. warning:: - - The code inserted by the function :code:`chill_delegated_block` **should be html safe**. You are encouraged to use an instance of the templating engine (aka Twig) to produce clean html. - -Example : - -.. code-block:: php - - namespace Chill\GroupBundle\Events; - - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent; - - - class TemplatingPostVerticalMenuEventSubscriber implements EventSubscriberInterface - { - // constructor logic will take place here in a real world - - public static function getSubscribedEvents() - { - return array('chill_block.person_post_vertical_menu' => array( - array('processRendering', 0) // you may change the priority if you want your content to be inserted upper or below of the other content. - )); - } - - // here is where we add content to the event. - public function processRendering(DelegatedBlockRenderingEvent $event) - { - // we access the person using $event['person'] - $memberships = $memberships = $this->membershipRepository - ->findBy(array('person' => $event['person'])); - - // we add content to the templating using the templating engine - $event->addContent( - $this->templating - ->render('ChillGroupBundle:Membership:short_listing.html.twig', array( - 'memberships' => $memberships, - )) - ); - } - - } - -This tag is registered as a service : - -.. code-block:: yaml - - services: - chill_group.membership_rendering_event: - class: Chill\GroupBundle\Events\TemplatingPostVerticalMenuEventSubscriber - tags: - - { name: kernel.event_subscriber } - -You should have a look at the documentation of the bundle to know which delegated block are available and what is their context. diff --git a/source/development/user-interface/widgets.rst b/source/development/user-interface/widgets.rst new file mode 100644 index 000000000..319ad3190 --- /dev/null +++ b/source/development/user-interface/widgets.rst @@ -0,0 +1,334 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Widgets +############################################## + +Rationale +========= + +Widgets are useful if you want to publish content on a page provided by another bundle. + + +Examples : + +- you want to publish a list of people on the homepage ; +- you may want to show task in the top bar, only if the bundle "task" is installed (**Note**: this bundle does not exists... yet !) +- you may want to show the group belonging (see :ref:`group-bundle`) below of the vertical menu, only if the bundle is installed. + +The administrator of the chill instance may configure the presence of widget. Although, some widget are defined by default (see `prepending the configuration of the bundle which define the place `_). + +Concepts +======== + +A bundle may define *place(s)* where a widget may be rendered. + +In a single *place*, zero, one or more *widget* may be displayed. + +Some *widget* may require some *configuration*, and some does not require any configuration. + +Example: + +=========================================== ======== ============================= ======================================= +Use case place place defined by... widget provided by... +=========================================== ======== ============================= ======================================= +Publishing a list of people on the homepage homepage defined by :ref:`main-bundle` widget provided by :ref:`person-bundle` +=========================================== ======== ============================= ======================================= + + + +Creating a widget without configuration +======================================== + +To add a widget, you should : + +- define your widget, implementing :class:`Chill\MainBundle\Templating\Widget\WidgetInterface` ; +- declare your widget with tag `chill_widget`. + + +Define the widget class +----------------------- + +Define your widget class by implemeting :class:`Chill\MainBundle\Templating\Widget\WidgetInterface`. + +Example : + +.. code-block:: php + + namespace Chill\PersonBundle\Widget; + + use Chill\MainBundle\Templating\Widget\WidgetInterface; + + + /** + * Add a button "add a person" + * + */ + class AddAPersonWidget implements WidgetInterface + { + public function render( + \Twig_Environment $env, + $place, + array $context, + array $config + ) { + // this will render a link to the page "add a person" + return $env->render("ChillPersonBundle:Widget:homepage_add_a_person.html.twig"); + } + } + +Arguments are : + +- :code:`$env` the :class:`\Twig_Environment`, which you can use to render your widget ; +- :code:`$place` a string representing the place where the widget is rendered ; +- :code:`$context` the context given by the template ; +- :code:`$config` the configuration which is, in this case, always an empty array (see :ref:`creating-a-widget-with-config`). + +.. note:: + + The html returned by the :code:`render` function will be considered as html safe. You should strip html before returning it. See also `How to escape output in template `_. + + +Declare your widget +------------------- + +Declare your widget as a service and add it the tag :code:`chill_widget`: + +.. code-block:: yaml + + service: + chill_person.widget.add_person: + class: Chill\PersonBundle\Widget\AddAPersonWidget + tags: + - { name: chill_widget, alias: add_person, place: homepage } + + +The tag must contains those arguments : + +- :code:`alias`: an alias, which will be used to reference the widget into the config +- :code:`place`: a place where this widget is authorized + +If you want your widget to be available on multiple places, you should add one tag with each place. + +Conclusion +---------- + +Once your widget is correctly declared, your widget should be available in configuration. + +.. code-block:: bash + + $ php app/console config:dump-reference chill_main + # Default configuration for extension with alias: "chill_main" + chill_main: + [...] + # register widgets on place "homepage" + homepage: + + # the ordering of the widget. May be a number with decimal + order: ~ # Required, Example: 10.58 + + # the widget alias (see your installed bundles config). Possible values are (maybe incomplete) : person_list, add_person + widget_alias: ~ # Required + +If you want to add your widget by default, see :ref:`declaring-widget-by-default`. + +.. _creating-a-widget-with-config: + +Creating a widget **with** configuration +======================================== + +You can declare some configuration with your widget, which allow administrators to add their own configuration. + +To add some configuration, you will : + +- declare a widget as defined above ; +- optionnaly declare it as a service ; +- add a widget factory, which will add configuration to the bundle which provide the place. + + +Declare your widget class +------------------------- + +Declare your widget. You can use some configuration elements in your process, as used here : + +.. literalinclude:: ./widgets/ChillPersonAddAPersonWidget.php + :language: php + +Declare your widget as a service +-------------------------------- + +You can declare your widget as a service. Not tag is required, as the service will be defined by the :code:`Factory` during next step. + + +.. code-block:: yaml + + services: + chill_person.widget.person_list: + class: Chill\PersonBundle\Widget\PersonListWidget + arguments: + - "@chill.person.repository.person" + - "@doctrine.orm.entity_manager" + - "@chill.main.security.authorization.helper" + - "@security.token_storage" + # this widget is defined by the PersonListWidgetFactory + +You can eventually skip this step and declare your service into the container through the factory (see above). + +Declare your widget factory +--------------------------- + +The widget factory must implements `Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface`. For your convenience, an :class:`Chill\MainBundle\DependencyInjection\Widget\Factory\AbstractWidgetFactory` will already implements some easy method. + +.. literalinclude:: ./widgets/ChillPersonAddAPersonListWidgetFactory.php + :language: php + +.. note:: + You can declare your widget into the container by overriding the `createDefinition` method. By default, this method will return the already existing service definition with the id given by :code:`getServiceId`. But you can create or adapt programmatically the definition. `See the symfony doc on how to do it `_. + + .. code-block:: php + + public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config) + { + $definition = new \Symfony\Component\DependencyInjection\Definition('my\Class'); + // create or adapt your definition here + + return $definition; + } + +You must then register your factory into the :code:`Extension` class which provide the place. This is done in the :code: `Bundle` class. + +.. code-block:: php + + # Chill/PersonBundle/ChillPersonBundle.php + + use Symfony\Component\HttpKernel\Bundle\Bundle; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Chill\PersonBundle\Widget\PersonListWidgetFactory; + + class ChillPersonBundle extends Bundle + { + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->getExtension('chill_main') + ->addWidgetFactory(new PersonListWidgetFactory()); + } + } + +.. _declaring-widget-by-default: + +Declaring a widget by default +============================= + +Use the ability `to prepend configuration of other bundle `_. A living example here : + +.. literalinclude:: ./widgets/ChillPersonExtension.php + :language: php + + +Defining a place +================ + +Add your place in template +-------------------------- + +A place should be defined by using the :code:`chill_widget` function, which take as argument : + +- :code:`place` (string) a string defining the place ; +- :code:`context` (array) an array defining the context. + +The context should be documented by the bundle. It will give some information about the context of the page. Example: if the page concerns a people, the :class:`Chill\PersonBundle\Entity\Person` class will be in the context. + +Example : + +.. code-block:: html+jinja + + {# an empty context on homepage #} + {{ chill_widget('homepage', {} }} + +.. code-block:: html+jinja + + {# defining a place 'right column' with the person currently viewed + {{ chill_widget('right_column', { 'person' : person } }} + + +Declare configuration for you place +----------------------------------- + +In order to let other bundle, or user, to define the widgets inside the given place, you should open a configuration. You can use the Trait :class:`Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait`, which provide the method `addWidgetConfiguration($place, ContainerBuilder $container)`. + +Example : + +.. literalinclude:: ./widgets/ChillMainConfiguration.php + :language: php + :emphasize-lines: 17, 30, 32, 52 + :linenos: + +.. _example-chill-main-extension: + +You should also adapt the :class:`DependencyInjection\*Extension` class to add ContainerBuilder and WidgetFactories : + +.. literalinclude:: ./widgets/ChillMainExtension.php + :language: php + :emphasize-lines: 25-39, 48-49, 56 + :linenos: + +- line 25-39: we implements the method required by :class:`Chill\MainBundle\DependencyInjection\Widget\HasWidgetExtensionInterface` ; +- line 48-49: we record the configuration of widget into container's parameter ; +- line 56 : we create an instance of :class:`Configuration` (declared above) + +Compile the possible widget using Compiler pass +----------------------------------------------- + +For your convenience, simply extends :class:`Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass`. This class provides a `doProcess(ContainerBuildere $container, $extension, $parameterName)` method which will do the job for you: + +- :code:`$container` is the container builder +- :code:`$extension` is the extension name +- :code:`$parameterName` is the name of the parameter which contains the configuration for widgets (see :ref:`the example with ChillMain above `. + +.. code-block:: php + + namespace Chill\MainBundle\DependencyInjection\CompilerPass; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; + + /** + * Compile the service definition to register widgets. + * + */ + class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { + + public function process(ContainerBuilder $container) + { + $this->doProcess($container, 'chill_main', 'chill_main.widgets'); + } + } + +As explained `in the symfony docs `_, you should register your Compiler Pass into your bundle : + +.. code-block:: php + + namespace Chill\MainBundle; + + use Symfony\Component\HttpKernel\Bundle\Bundle; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass; + + + class ChillMainBundle extends Bundle + { + public function build(ContainerBuilder $container) + { + parent::build($container); + $container->addCompilerPass(new WidgetsCompilerPass()); + } + } + + diff --git a/source/development/user-interface/widgets/ChillMainConfiguration.php b/source/development/user-interface/widgets/ChillMainConfiguration.php new file mode 100644 index 000000000..2cc893cc3 --- /dev/null +++ b/source/development/user-interface/widgets/ChillMainConfiguration.php @@ -0,0 +1,64 @@ +setWidgetFactories($widgetFactories); + // we will need the container builder later... + $this->containerBuilder = $containerBuilder; + } + + /** + * {@inheritDoc} + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('chill_main'); + + $rootNode + ->children() + + // ... + + ->arrayNode('widgets') + ->canBeDisabled() + ->children() + // we declare here all configuration for homepage place + ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder)) + ->end() // end of widgets/children + ->end() // end of widgets + ->end() // end of root/children + ->end() // end of root + ; + + + return $treeBuilder; + } + } + + diff --git a/source/development/user-interface/widgets/ChillMainExtension.php b/source/development/user-interface/widgets/ChillMainExtension.php new file mode 100644 index 000000000..4370f33f2 --- /dev/null +++ b/source/development/user-interface/widgets/ChillMainExtension.php @@ -0,0 +1,59 @@ +widgetFactories[] = $factory; + } + + /** + * + * @return WidgetFactoryInterface[] + */ + public function getWidgetFactories() + { + return $this->widgetFactories; + } + + public function load(array $configs, ContainerBuilder $container) + { + // configuration for main bundle + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + // add the key 'widget' without the key 'enable' + $container->setParameter('chill_main.widgets', + array('homepage' => $config['widgets']['homepage'])); + + // ... + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new Configuration($this->widgetFactories, $container); + } + +} diff --git a/source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php b/source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php new file mode 100644 index 000000000..e5bd236c9 --- /dev/null +++ b/source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php @@ -0,0 +1,69 @@ +booleanNode('only_active') + ->defaultTrue() + ->end(); + $node->integerNode('number_of_items') + ->defaultValue(50) + ->end(); + $node->scalarNode('filtering_class') + ->defaultNull() + ->end(); + + } + + /* + * return an array with the allowed places where the widget can be rendered + * + * @return string[] + */ + public function getAllowedPlaces() + { + return array('homepage'); + } + + /* + * return the widget alias + * + * @return string + */ + public function getWidgetAlias() + { + return 'person_list'; + } + + /* + * return the service id for the service which will render the widget. + * + * this service must implements `Chill\MainBundle\Templating\Widget\WidgetInterface` + * + * the service must exists in the container, and it is not required that the service + * has the `chill_main` tag. + */ + public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config) + { + return 'chill_person.widget.person_list'; + } + +} + diff --git a/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php b/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php new file mode 100644 index 000000000..876848d23 --- /dev/null +++ b/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php @@ -0,0 +1,129 @@ +personRepository = $personRepostory; + $this->authorizationHelper = $authorizationHelper; + $this->tokenStorage = $tokenStorage; + $this->entityManager = $em; + } + + /** + * + * @param type $place + * @param array $context + * @param array $config + * @return string + */ + public function render(\Twig_Environment $env, $place, array $context, array $config) + { + $qb = $this->personRepository + ->createQueryBuilder('person'); + + // show only the person from the authorized centers + $and = $qb->expr()->andX(); + $centers = $this->authorizationHelper + ->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE)); + $and->add($qb->expr()->in('person.center', ':centers')); + $qb->setParameter('centers', $centers); + + + // add the "only active" where clause + if ($config['only_active'] === true) { + $qb->join('person.accompanyingPeriods', 'ap'); + $or = new Expr\Orx(); + // add the case where closingDate IS NULL + $andWhenClosingDateIsNull = new Expr\Andx(); + $andWhenClosingDateIsNull->add((new Expr())->isNull('ap.closingDate')); + $andWhenClosingDateIsNull->add((new Expr())->gte(':now', 'ap.openingDate')); + $or->add($andWhenClosingDateIsNull); + // add the case when now is between opening date and closing date + $or->add( + (new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate') + ); + $and->add($or); + $qb->setParameter('now', new \DateTime(), Type::DATE); + } + + // adding the where clause to the query + $qb->where($and); + + $qb->setFirstResult(0)->setMaxResults($config['number_of_items']); + + $persons = $qb->getQuery()->getResult(); + + return $env->render( + 'ChillPersonBundle:Widget:homepage_person_list.html.twig', + array('persons' => $persons) + ); + } + + /** + * + * @return UserInterface + */ + private function getUser() + { + // return a user + } + +} diff --git a/source/development/user-interface/widgets/ChillPersonExtension.php b/source/development/user-interface/widgets/ChillPersonExtension.php new file mode 100644 index 000000000..869931184 --- /dev/null +++ b/source/development/user-interface/widgets/ChillPersonExtension.php @@ -0,0 +1,51 @@ +prependExtensionConfig('chill_main', array( + 'widgets' => array( + 'homepage' => array( + array( + 'widget_alias' => 'add_person', + 'order' => 2 + ) + ) + ) + )); + } +} +