How to add a tooltip in OroCRM

Any application should be unambiguous. Every user should easily read and understand it. OroCRM applications contain many forms and fields. Developers can customize them and add even more, which can make the application difficult to use by other parties. The easiest way to avoid this is to add comments to the application elements: fields, forms etc. This can be done with tooltips in OroCRM. And we are going to show you how to do it.

In the following example we will use a Contact form entity. You can see the form in contact/create or contact/update/(id). You can use any of the forms but if you want to add some changes to a native Oro form, you first need to overwrite it. We have overwritten the OroCRMContact form and now can add tooltips to fields. Keep in mind, the native OroCRMContact form class is vendor/oro/crm/src/OroCRM/Bundle/ContactBundle/Form/Type/ContactType.php. We need to change the $builder->add() method.

Here is how you can add fields to the Oro form:

protected function buildPlainFields(FormBuilderInterface $builder, array $options)
{
   // basic plain fields
   $builder
       ->add('namePrefix', 'text', array('required' => false, 'label' => 'orocrm.contact.name_prefix.label'))
       ->add('firstName', 'text', array('required' => false, 'label' => 'orocrm.contact.first_name.label'))
       ->add('middleName', 'text', array('required' => false, 'label' => 'orocrm.contact.middle_name.label'))
       ->add('lastName', 'text', array('required' => false, 'label' => 'orocrm.contact.last_name.label'))
       ->add('nameSuffix', 'text', array('required' => false, 'label' => 'orocrm.contact.name_suffix.label'))
       ->add('gender', 'oro_gender', array('required' => false, 'label' => 'orocrm.contact.gender.label'))
       ->add('birthday', 'oro_date', array('required' => false, 'label' => 'orocrm.contact.birthday.label'))
       ->add(
           'description',
           'oro_resizeable_rich_text',
           array(
               'required' => false,
               'label' => 'orocrm.contact.description.label',
               'tooltip' => 'Test description'
           )
       );

   $builder
       ->add('jobTitle', 'text', array('required' => false, 'label' => 'orocrm.contact.job_title.label'))
       ->add('fax', 'text', array('required' => false, 'label' => 'orocrm.contact.fax.label'))
       ->add('skype', 'text', array('required' => false, 'label' => 'orocrm.contact.skype.label'));

   $builder
       ->add('twitter', 'text', array('required' => false, 'label' => 'orocrm.contact.twitter.label'))
       ->add('facebook', 'text', array('required' => false, 'label' => 'orocrm.contact.facebook.label'))
       ->add('googlePlus', 'text', array('required' => false, 'label' => 'orocrm.contact.google_plus.label'))
       ->add('linkedIn', 'text', array('required' => false, 'label' => 'orocrm.contact.linked_in.label'))
       ->add(
           'picture',
           'oro_image',
           array(
               'label'          => 'orocrm.contact.picture.label',
               'required'       => false
           )
       );
}

We can add tooltips to all the fields. But in this example we will add tooltips just to “firstName” and “lastName”.

protected function buildPlainFields(FormBuilderInterface $builder, array $options)
{
   // basic plain fields
   $builder
   ->add('namePrefix', 'text', array('required' => false, 'label' => 'orocrm.contact.name_prefix.label'))
   ->add('firstName', 'text',
       array(
           'required' => false,
           'label' => 'orocrm.contact.first_name.label',
           'tooltip' => 'Our tooltip for first name'
       ))
   ->add('middleName', 'text', array('required' => false, 'label' => 'orocrm.contact.middle_name.label'))
   ->add('lastName', 'text',
       array(
           'required' => false,
           'label' => 'orocrm.contact.last_name.label',
           'tooltip' => 'Our tooltip for last name'
       ))
   ->add('nameSuffix', 'text', array('required' => false, 'label' => 'orocrm.contact.name_suffix.label'))
   ->add('gender', 'oro_gender', array('required' => false, 'label' => 'orocrm.contact.gender.label'))
   ->add('birthday', 'oro_date', array('required' => false, 'label' => 'orocrm.contact.birthday.label'))
   ->add(
       'description',
       'oro_resizeable_rich_text',
       array(
           'required' => false,
           'label' => 'orocrm.contact.description.label'
       )
   );

   $builder
       ->add('jobTitle', 'text', array('required' => false, 'label' => 'orocrm.contact.job_title.label'))
       ->add('fax', 'text', array('required' => false, 'label' => 'orocrm.contact.fax.label'))
       ->add('skype', 'text', array('required' => false, 'label' => 'orocrm.contact.skype.label'));

   $builder
       ->add('twitter', 'text', array('required' => false, 'label' => 'orocrm.contact.twitter.label'))
       ->add('facebook', 'text', array('required' => false, 'label' => 'orocrm.contact.facebook.label'))
       ->add('googlePlus', 'text', array('required' => false, 'label' => 'orocrm.contact.google_plus.label'))
       ->add('linkedIn', 'text', array('required' => false, 'label' => 'orocrm.contact.linked_in.label'))
       ->add(
           'picture',
           'oro_image',
           array(
               'label'          => 'orocrm.contact.picture.label',
               'required'       => false
           )
       );
}

We added only one property to the builder and now we have tooltips for two fields.

Create contact form screenshot

Here is the message of the tooltip:

Create contact form screenshot with tooltips

We can also add tooltips to the other parts of the application like view action. For this we only need to add few lines to a twig template.

For example, we need to add a tooltip to the Contact view page for “Social” links. First, we need to overwrite the template and add some code. The template of the Contact info widget is located in vendor/oro/crm/src/OroCRM/Bundle/ContactBundle/Resources/views/Contact/widget/info.html.twig
We should add the code of our tooltip, set the twig variable with tooltip code and the label of the field.

{% set SocialBlockLabel %}
   <div class="control-group tooltip-title-block">
       <label class="control-label">
           {{ ui.tooltip('Social links', {}, 'bottom') }}
           {{ 'orocrm.contact.social.label'|trans }}
       </label>
   </div>
{% endset %}

Now we replace a string title with our twig variable “SocialBlockLabel”.

{{ ui.renderHtmlProperty('orocrm.contact.social.label'|trans, socialData) }}

To:

{{ ui.renderHtmlProperty(SocialBlockLabel, socialData) }}

Now we need to initialize the tooltip: we should add a JS script to the bottom of the template:

<script type="text/javascript">
   require(['jquery', 'oroui/js/layout'],
           function($, layout){
               layout.initPopover($('.tooltip-title-block'));
           });
</script>

Important: if you add tooltips to custom templates, make sure the templates contain:

{% import 'OroUIBundle::macros.html.twig' as ui %}

Here is the result of the template with our changes:

{% import 'OroUIBundle::macros.html.twig' as ui %}
{% import 'OroEntityConfigBundle::macros.html.twig' as entityConfig %}
{% import 'OroUserBundle::macros.html.twig' as U %}

{% macro renderCollectionWithPrimaryElement(collection, isEmail, entity) %}
   {% import 'OroUIBundle::macros.html.twig' as ui %}
   {% import 'OroEmailBundle::macros.html.twig' as email %}

   {% set primaryElement = null %}
   {% set elements = [] %}

   {% for element in collection %}
       {% if element.primary %}
           {% set primaryElement = element %}
       {% else %}
           {% set elements = elements|merge([element]) %}
       {% endif %}
   {% endfor %}
   {% if primaryElement %}
       {% set elements = [primaryElement]|merge(elements) %}
   {% endif %}

   <ul class="extra-list">
       {%- for element in elements %}
           <li class="contact-collection-element{% if element.primary %} primary{% endif %}">
               {% if isEmail %}
                   {{ email.renderEmailWithActions(element, entity) }}
               {% else %}
                   {{ ui.renderPhoneWithActions(element, entity) }}
               {% endif %}
           </li>
       {% endfor -%}
   </ul>
{% endmacro %}

{%- macro getSocialUrl(type, value) -%}
   {% if value|slice(0, 5) == 'http:' or value|slice(0, 6) == 'https:' %}
       {{ value }}
   {% else %}
       {{ oro_social_url(type, value) }}
   {% endif %}
{%- endmacro -%}

<div class="widget-content">
   <div class="row-fluid form-horizontal contact-info">
       <div class="responsive-block">
           {%- set skypeData -%}
               {%- if entity.skype -%}
                   {{ entity.skype }} {{ skype_button(entity.skype) }}
               {%- endif -%}
           {%- endset -%}

           {%- set socialData -%}
               {%- if entity.twitter or entity.facebook or entity.googlePlus or entity.linkedIn -%}
                   <ul class="list-inline">
                       {% if entity.twitter %}
                           <li>
                               <a class="no-hash" href="{{ _self.getSocialUrl('twitter', entity.twitter) }}" target="_blank" title="Twitter">
                                   <i class="icon-twitter"></i>
                               </a>
                           </li>
                       {% endif %}
                       {% if entity.facebook %}
                           <li>
                               <a class="no-hash" href="{{ _self.getSocialUrl('facebook', entity.facebook) }}" target="_blank" title="Facebook">
                                   <i class="icon-facebook"></i>
                               </a>
                           </li>
                       {% endif %}
                       {% if entity.googlePlus %}
                           <li>
                               <a class="no-hash" href="{{ _self.getSocialUrl('google_plus', entity.googlePlus) }}" target="_blank" title="Google+">
                                   <i class="icon-google-plus"></i>
                               </a>
                           </li>
                       {% endif %}
                       {% if entity.linkedIn %}
                           <li>
                               <a class="no-hash" href="{{ _self.getSocialUrl('linked_in', entity.linkedIn) }}" target="_blank" title="LinkedIn">
                                   <i class="icon-linkedin"></i>
                               </a>
                           </li>
                       {% endif %}
                   </ul>
               {%- endif -%}
           {%- endset -%}

           {% set SocialBlockLabel %}
               <div class="control-group tooltip-title-block">
                   <label class="control-label">
                       {{ ui.tooltip('Social links', {}, 'bottom') }}
                       {{ 'orocrm.contact.social.label'|trans }}
                   </label>
               </div>
           {% endset %}

           {{ ui.renderHtmlProperty('orocrm.contact.description.label'|trans, entity.description) }}
           {{ ui.renderHtmlProperty('orocrm.contact.phones.label'|trans, entity.phones.count ? _self.renderCollectionWithPrimaryElement(entity.phones, false, entity) : null) }}
           {{ ui.renderHtmlProperty('orocrm.contact.emails.label'|trans, entity.emails.count ? _self.renderCollectionWithPrimaryElement(entity.emails, true, entity) : null) }}
           {{ ui.renderHtmlProperty('orocrm.contact.fax.label'|trans, entity.fax ? ui.renderPhone(entity.fax) : null) }}
           {{ ui.renderHtmlProperty('orocrm.contact.skype.label'|trans, skypeData) }}
           {{ ui.renderProperty('orocrm.contact.method.label'|trans, entity.method) }}
           {{ ui.renderHtmlProperty(SocialBlockLabel, socialData) }}

           {{ entityConfig.renderDynamicFields(entity) }}
       </div>

       <div class="responsive-block">
           {%- set accountsData -%}
               {%- if entity.accounts.count -%}
                   {% set accountViewGranted = resource_granted('orocrm_account_view') %}
                   {%- for account in entity.accounts -%}
                       {%- if accountViewGranted -%}
                           <a href="{{ path('orocrm_account_view', {'id': account.id}) }}">{{ ui.renderEntityViewLabel(account, 'name', 'orocrm.account.entity_label') }}</a>
                       {%- else -%}
                           {{ ui.renderEntityViewLabel(account, 'name') }}
                       {%- endif -%}
                       {% if not loop.last %}, {% endif %}
                   {%- endfor -%}
               {%- endif -%}
           {%- endset -%}

           {%- set birthdayData -%}
               {{ entity.birthday is not empty ? entity.birthday|oro_format_date : null }}
               {% if entity.birthday is not empty %} ({{ entity.birthday|age_string({'default': 'N/A'}) }}){% endif %}
           {%- endset -%}

           {%- set assignedToData -%}
               {%- if entity.assignedTo -%}
                   {{ U.render_user_name(entity.assignedTo) }}
                   {{ U.user_business_unit_name(entity.assignedTo) }}
               {%- endif -%}
           {%- endset -%}

           {%- set reportsToData -%}
               {%- if entity.reportsTo -%}
                   <a href="{{ path('orocrm_contact_view', {'id': entity.reportsTo.id}) }}">
                       {{ entity.reportsTo }}
                   </a>
               {%- endif -%}
           {%- endset -%}

           {{ ui.renderProperty('orocrm.contact.job_title.label'|trans, entity.jobTitle) }}
           {{ ui.renderHtmlProperty('orocrm.contact.accounts.label'|trans, accountsData) }}
           {{ ui.renderProperty('orocrm.contact.birthday.label'|trans, entity.birthday ? birthdayData : null) }}
           {{ ui.renderProperty('orocrm.contact.gender.label'|trans, oro_gender(entity.gender)) }}
           {{ ui.renderProperty('orocrm.contact.source.label'|trans, entity.source) }}
           {{ ui.renderHtmlProperty('orocrm.contact.assigned_to.label'|trans, assignedToData) }}
           {{ ui.renderHtmlProperty('orocrm.contact.reports_to.label'|trans, reportsToData) }}
           {{ ui.renderProperty('orocrm.contact.groups.label'|trans, entity.groups.count ? entity.groupLabels|join(', ') : null) }}
       </div>
   </div>
</div>

<script type="text/javascript">
   require(['jquery', 'oroui/js/layout'],
           function($, layout){
               layout.initPopover($('.tooltip-title-block'));
           });
</script>

Contact view with social tooltip

We can also add tooltips to the forms of System config. For example, in the config/system/platform/tracking config we can see that each field has a tooltip. We can add tooltips to fields by simply adding a “tooltip” property to the system_configuration.yml file when we add a new config section. Like in this example in Tracking:

Tooltip configuration screenshot

File: vendor/oro/platform/src/Oro/Bundle/TrackingBundle/Resources/config/system_configuration.yml

oro_tracking.dynamic_tracking_enabled:
   data_type: boolean
   type: oro_config_checkbox
   priority: 50
   options:
       label: oro.tracking.system_configuration.fields.dynamic_tracking_enabled.label
       tooltip: oro.tracking.form.tooltip.dynamic_tracking_enabled
       required: true

Now you know that adding tooltips is very easy. You do not need custom javascript solutions for specific parts of the application. You only need to add few lines of code and clear cache afterwards. And this can ensure that your application is user-friendly.