.. 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 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 :ref:`declaring-widget-by-default`). 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()); } }