If you need to change the appearance of your application, or just insert some specific data – you have a few ways to do it. The most popular method is to use native Symfony ways. You can overwrite a part of the logic from a bundle and replace a template with it. You can also overwrite a template by creating a similar template in the app/Resource… Another way is adding extra code to the native template, but it is a really bad practice, so try to avoid it.
Fortunately we have a UI bundle in OroCRM. The UiBundle in OroCRM is very flexible. In this post we want to show you how you can add your own data blocks to the appearance of your application without overwriting any view.
In order to add data blocks to OroCRM application we can use “placeholders”. Placeholders can be defined in any bundle inside the placeholders.yml file, which is stored in the bundle’s Resources/config directory.
For example we can take a look at placeholders.yml of the Account bundle
#vendor/oro/crm/src/OroCRM/Bundle/AccountBundle/Resources/config/placeholders.yml placeholders: quick_launchpad_left_column: items: accounts_launchpad: order: 10 page_header_stats_after: items: account_lifetime_value: order: 20 items: accounts_launchpad: template: OroCRMAccountBundle:Dashboard:accountsLaunchpad.html.twig account_lifetime_value: template: OroCRMAccountBundle:Account:lifetimeValue.html.twig applicable: @oro_ui.placeholder.filter->isInstanceOf($entity$, %orocrm_account.account.entity.class%)
There are two sections – “placeholders” and “items”. In the “placeholders” we choose which placeholder section will be used in the twig template, and the items are listed in the “items” .
The “items” option is a map that is used to create items of placeholders, which will then later be assigned to different placeholders. We can use one item in different placeholders. Each name of an item is a unique key, which will later be used to refer to a specific item. The value is a map that controls how an item is rendered when being assigned to a placeholder. Each item can have additional options:
- acl: The item will only be rendered if the user is granted access to the configured access control list.
- action: An action of a controller that will be called when rendering an item. This option is useful when some complex logic needs to be executed when rendering the placeholder item.
- applicable: A filter that will be evaluated at runtime to determine whether an item should be rendered or not.
- template: A template reference that will be included when an item is rendered.
In our example we have two placeholders: “quick_launchpad_left_column” and “page_header_stats_after”. These placeholders are in the template
vendor/oro/platform/src/Oro/Bundle/DashboardBundle/Resources/views/Index/quickLaunchpad.html.twig
{% block widgets %} <div class="responsive-cell dashboard-column"> {% placeholder quick_launchpad_left_column %} {#placeholder in twig template#} </div> <div class="responsive-cell dashboard-column"> {% placeholder quick_launchpad_right_column %} </div> {% endblock widgets %}
And in vendor/oro/platform/src/Oro/Bundle/UIBundle/Resources/views/actions/view.html.twig
<div> <ul class="inline"> {% placeholder page_header_stats_before with {entity: entity} %} {% block stats %} <li>{{ 'oro.ui.created_at'|trans }}: {{ breadcrumbs.entity.createdAt is defined and breadcrumbs.entity.createdAt ? breadcrumbs.entity.createdAt|oro_format_datetime : 'N/A' }}</li> <li>{{ 'oro.ui.updated_at'|trans }}: {{ breadcrumbs.entity.updatedAt is defined and breadcrumbs.entity.updatedAt ? breadcrumbs.entity.updatedAt|oro_format_datetime : 'N/A' }}</li> {% endblock stats %} {% placeholder page_header_stats_after with {entity: entity} %} {#placeholder in twig template#} {% if isMobileVersion() and entity is not null %} {% set ownerLink = UI.entityOwnerLink(entity) %} <li>{{ ownerLink }}{% placeholder additional_owner_info with {entity: entity} %}</li> {% endif %} </ul> </div>
We see that placeholders section is just a part of the twig template. It is a container for all the inserted items that were appointed in a placeholders.yml.
Also “page_header_stats_after” placeholder requires a parameter “entity”.
So if we look at placeholders.yml of the Account bundle we can see what will be inserted in the placeholders.
For accounts_launchpad item we insert accountsLaunchpad.html.twig template without any additional conditions.
vendor/oro/crm/src/OroCRM/Bundle/AccountBundle/Resources/views/Dashboard/accountsLaunchpad.html.twig
{% extends 'OroDashboardBundle:Dashboard:launchpad.html.twig' %} {% set widgetName = 'accounts_launchpad' %} {% set widgetLabel = 'orocrm.account.entity_plural_label'|trans %} {% set widgetIcon = 'suitcase' %} {% set widgetAcl = 'orocrm_account_view' %} {% set items = { index: { label: 'oro.dashboard.list'|trans, route: 'orocrm_account_index', acl: 'orocrm_account_view' }, create: { label: 'oro.ui.create_entity'|trans({'%entityName%': 'orocrm.account.entity_label'|trans}), route: 'orocrm_account_create', acl: 'orocrm_account_create' } } %}
But for “account_lifetime_value” we have a filter. As the placeholder has parameter “entity” we can insert the item only then the entity instance of OroCRM\Bundle\AccountBundle\Entity\Account class.
vendor/oro/crm/src/OroCRM/Bundle/AccountBundle/Resources/views/Account/lifetimeValue.html.twig
<li> <div class="label label-info orocrm-channel-lifetime-value-label"> {{ 'orocrm.account.lifetime_value.label'|trans }}: {{ orocrm_channel_account_lifetime(entity)|oro_format_currency }} </div> </li>
It may seem hard, but the following example will make it easier.
Let’s take a look at the “segments/view”. There are two buttons: Edit and Delete. We will add a Cancel button. The button should return me to the segments list.
First we need to create a template with the button.
{% import 'OroUIBundle::macros.html.twig' as UI %} {{ UI.cancelButton( path('oro_segment_index') ) }}
In order to do so we need to investigate which placeholder can be used for it. I personally think that the best way is to use the “view_navButtons_before”. The placeholder is in line 24 of vendor/oro/platform/src/Oro/Bundle/UIBundle/Resources/views/actions/view.html.twig
{% placeholder view_navButtons_before with {entity: entity} %}
Also we can see that the placeholder requires a parameter entity, which means that we can use filters in the placeholder configuration. So we can add few lines to any placeholders.yml in your bundles.
placeholders: view_navButtons_before: items: my_cancel_button: order: 10 items: my_cancel_button: template: AtwixTestBundle:Test:my_cancel_button.html.twig applicable: @oro_ui.placeholder.filter->isInstanceOf($entity$, %oro_segment.segment.entity.class%)
After refreshing the page we can see an additional button “Cancel”. We chose the left position, so we use small sort order value for our placeholder’s item.
As you can see we added a simple functionality to our oroCRM, which was really simple. Whenever you need to add any functionality or data block to the application you have to think about placeholders, because it is best way to do it fast and simple. Also placeholders in OroCRM allow you to avoid dependencies in native or custom bundles.
You may also want to read: