Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev/core/#101: Allow further customization of search form in hooks #12078

Merged

Conversation

mickadoo
Copy link
Contributor

@mickadoo mickadoo commented May 3, 2018

Overview

The contact search form uses the template templates/CRM/Contact/Form/Search/Criteria/Basic.tpl. In the template there are already some conditionals to hide certain fields, such as

{if $form.group}
  <td>
  ...
{/if}

However only some fields are covered. For an extension developer who wants to hide more fields here it is difficult without causing warnings or breaking the layout.

To make it easier conditionals should be applied to each form element.

Before

Form conditionals were only available for some form elements in the templates/CRM/Contact/Form/Search/Criteria/Basic.tpl template

What it looked like before:

before

After

Form conditionals are available for all form elements in the templates/CRM/Contact/Form/Search/Criteria/Basic.tpl template

What it looked like after (with no fields changed, should be the same as "Before")

after_with_all_options

What it looked like after with the following fields removed
  • email
  • job_title
  • external_identifier
  • privacy_toggle
  • email_on_hold
  • preferred_communication_method

after_without_fields

Notes

I reformatted the code a bit, fixing warnings from the IDE. I also changed some existing conditionals to not output anything if the element wasn't set. As it was before they would still show an empty td element leading to gaps in the form like:

conditional_tds

Without these empty td elements the gaps are filled:

conditional_elements

Gitlab Issue

https://lab.civicrm.org/dev/core/issues/101

@reneolivo
Copy link
Contributor

Reviewing

Copy link
Contributor

@reneolivo reneolivo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before

search-before

After

search-after
(please refer to comment about this issue)


Can you please provide some quick usage example on how you can hide these fields from a plugin? If you can do that I can replicate it locally.

Other than that the changes look good

<td>{$form.tag_search.label} {help id="id-all-tags"}<br />{$form.tag_search.html}</td>
{if $form.tag_search}
<td>{$form.tag_search.label} {help id="id-all-tags"}<br/>
{$form .tag_search.html}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ this is breaking the search because there's an extra space between $form and .tag_search.html. If you remove this space it fixes the issue.

@eileenmcnaughton
Copy link
Contributor

@michaelmcandrew it's perhaps not possible here but where I have been updating forms to be more changeable by hooks I have been doing it by assigning the fields to use as an array - e.g

4fb5fcf#diff-7f0dd843b42789babf5e6a63cde98198L72

(part of the motivation for an array approach is they can also be re-ordered and others can be asserted - ie the default becomes to add an item in the array with

        {$form.$element.label}<br/>
        {$form.$element.lhtml}

if there is no special handling for that field

@michaelmcandrew
Copy link
Contributor

@eileenmcnaughton, i guess you meant @mickadoo

@reneolivo
Copy link
Contributor

Hello @eileenmcnaughton I was investigating the structure of the template and the form controller and even though I agree that looping through each field would be more extensible, I concluded that it would be a bit difficult to implement.

These are the issues I found:

Since the solution you propose is not straight forward I suggest implementing this solution for now which works well with the current implementation of the advanced search template.

We could then draw a plan to make the advanced search templates more customizable as a whole.

@eileenmcnaughton
Copy link
Contributor

eileenmcnaughton commented May 8, 2018

@reneolivio - my main concern with this 'as is' is that there is a potential that anyone who took advantage of it would later feel burnt if we change it again.

Regarding "There's no way to tell the section each field belongs to nor the ones that belong to the basic section."

The way we've done it elsewhere (e.g. billing block) is more like
php

$this->assign('basicSearchFields' => ['sort_name', 'email', 'contact_type'....]);

and in the template (not correct code)

{foreach value=basicSearchField from=$basicSearchFields} 
  {if file_exists('CRM/Contact/SearchFieldTemplates/$basicSearchField)} 
    {include 'CRM/Contact/SearchFieldTemplates/$basicSearchField'}
  {/else}
    {$form.basicSearchField.label}<br/>
    {$form.basicSearchField.html}
  {/if}
{/foreach}

(I don't know if that file exists approach actually works - I was just thinking for a complex field it might be cleaner).

Regarding the labels - in my experience people don't define the labels in the tpl files because they couldn't put in the correct label when adding the field - they just copied another field done that way at some point in time. It shouldn't be too hard to fix those

@reneolivo reneolivo force-pushed the 101-conditionals-in-search-form branch 3 times, most recently from b4904b4 to 25ac93f Compare May 9, 2018 03:16
@reneolivo
Copy link
Contributor

Hello @eileenmcnaughton !!

I took a stab at your proposed solution, I'll update things here if you don't mind:

After

after-4
There's a bit of variation compared to the original, but it's minimal. Just the spacing between fields is about 5~10px different.

Technical details

  1. Some of the labels were moved from the template to the controller so the implementation would depend less on the template.
  2. The basic search fields were defined in the controller in the same order as the original implementation:
$form->assign('basicSearchFields', [
  'sort_name',
  'email',
  'contact_type',
  'group',
  'contact_tags',
  'tag_search',
  'all_tag_types',
  'phone_numeric',
  'phone_location_type_id',
  'phone_phone_type_id',
  'privacy_toggle',
  'preferred_communication_method',
  'contact_source',
  'job_title',
  'preferred_language',
  'contact_id',
  'external_identifier',
  'uf_user',
]);
  1. A smarty's template_exists method was made available on the template as a modifier:
$smarty = CRM_Core_Smarty::singleton();
$smarty->register_modifier('template_exists', array(&$smarty, 'template_exists'));
  1. The basic search template was changed to this:
<div class="advanced-search-fields basic-fields form-layout">
  {foreach from=$basicSearchFields item=basicSearchField}
    {assign var=field value=$form[$basicSearchField]}
    {if $field}
      {capture assign=fieldTemplatePath}CRM/Contact/Form/Search/Criteria/Fields/{$basicSearchField}.tpl{/capture}
      {if $fieldTemplatePath|template_exists}
        {include file=$fieldTemplatePath}
      {else}
        <div class="search-field">
          {$field.label}<br />
          {$field.html}
        </div>
      {/if}
    {/if}
  {/foreach}
</div>

Why divs ? Because when removing one of the fields it would leave a blank space at the end of the table. Also, like the contact_tags, would display a sibling field that could break the table as there was no planned space for such field.
I would have liked using something like Bootstrap, but it's still not supported. I decided to use CSS Grids instead:

.advanced-search-fields {
  display: grid;
  /* defines a column divided by 3 sections, each with -10px wide: */
  grid-template-columns: [col] repeat(3, calc(100% / 3 - 10px));
  width: 100%;
}

.advanced-search-fields .search-field {
  padding: 5px;
}

.advanced-search-fields .search-field__span-2 {
  grid-column: col / span 2;
}
.advanced-search-fields .search-field__span-3 {
  grid-column: col / span 3;
}

There's good support for CSS Grid across all major browsers, except for IE10 and lower. Not sure which version you are supporting.

  1. If there's a custom template defined for the field it includes it, otherwise it displays a generic field:
{capture assign=fieldTemplatePath}CRM/Contact/Form/Search/Criteria/Fields/{$basicSearchField}.tpl{/capture}
{if $fieldTemplatePath|template_exists}
  {include file=$fieldTemplatePath}
{else}
  <div class="search-field">
    {$field.label}<br />
    {$field.html}
  </div>
{/if}

A sample field template would be like this:

<div class="search-field">
  {$form.contact_tags.label}<br />
  {$form.contact_tags.html}
</div>

{if $isTagset}
  <div class="search-field search-field__span-2">
    {include file="CRM/common/Tagset.tpl"}
  </div>
{/if}

Please let me know if this approach is acceptable. Also, please guide me on how to test this on another extension. I know that by removing things from the basicSearchFields array it can be done, but I'm not 100% how the implementation would look like.

@eileenmcnaughton
Copy link
Contributor

Thanks @reneolivo - I'll loop in @colemanw re the css / layout & I'll take a close look at the code - with any luck I can figure out how to unit test it.

Also ping @monishdeb @seamuslee001 FYI

@eileenmcnaughton
Copy link
Contributor

test this please

@@ -35,6 +35,32 @@ class CRM_Contact_Form_Search_Criteria {
* @param CRM_Core_Form $form
*/
public static function basic(&$form) {
// Allows the template to determine if another template exists:
$smarty = CRM_Core_Smarty::singleton();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't work for me. What did work was

a) adding a new file called modifier.template_exists.php in packages/Smarty/plugins
b) then clearing smarty caches

I tried using
https://gitlab.athlonsofia.com/athlon/silla.io/blob/3b669c42e34d72d62f4ac624244d979402c7a98e/resources/smarty_plugins/modifier.template_exists.php

but it didn't work for me so am using

function smarty_modifier_template_exists($template)
{

  $templatePath = __DIR__ . '/../../../templates/' . $template;

  return is_file($templatePath);
}

If you want to put up a PR for a new modifer against the packages repo I will merge it

@eileenmcnaughton
Copy link
Contributor

@reneolivo I took a look at this & it seems pretty good. I have a proposal to move from flat field definitions to a fieldSpec - try compucorp@f4e095a

  • this allows definition of the help file & field in the definition & makes some of the field-specific templates redundant. It is also more extendable later.

Per my comment above - I had to add a modifier to packages to get the smarty include check working - but I'm leaning towards the field tpl being defined in the array (if needed ) ie.

      'preferred_language' => [],
      'contact_id' => ['help' => ['id' => 'id-contact-id', 'file' => 'CRM/Contact/Form/Contact']],
      'external_identifier' => ['help' => ['id' => 'id-external-id', 'file' => 'CRM/Contact/Form/Contact']],
      'uf_user' => ['tpl' => 'uf_user.tpl'],

It feels like a similar lever of complexity to doing the file_exists in smarty & would be overrideable from php which might be nice.

Other points

  1. I'm finding the way email_on_hold is in communication_preferences a bit hacky- but I'm prepared to let that one slide.
  2. I'm finding the {if $isTagset} a little confusing - I had it 'cut' with tag_search rather than contact_tags - but I think you may be right. However that would mean we don't need a field template for tag_search

Once you've checked the proposal / comments above we will ping Coleman to check the markup although it seems sane to me

eileenmcnaughton added a commit to eileenmcnaughton/civicrm-core that referenced this pull request May 14, 2018
…nsion.

This is an effort at a way to genericise core forms that basically exist to do crud on an entity. We have a
number of fairly straight forward forms of this type in core and, in order to allow extensions to use
custom fields on a range of entities we should support editing them on these core crud forms.

To add custom data support to an entity we need to
a) add the custom data to the form using CRM_Custom_Form_CustomData::addToForm($this);
b) ensure that the entity is saved using an api call not a BAO call
c) add the custom data block to the tpl file - ie  {include file="CRM/common/customDataBlock.tpl"}

(the above is possible due to previous work to add support & simplify)

In this PR the adding of the customData is done in the EntityFormTrait, the api is previously converted
and the custom data is included by using a generic tpl to support metadata applied to the form.

By using metadata on the form we can also give extension writers a lot more control over what
is on the form as they can add to, alter, or remove the metadata in a php hook. This
is the crux of this issue civicrm#12078 & it likewise is
looking to use a generic field template to add fields based on metadata. A key difference between the 2 prs
is that one uses divs & the other a table & that might preclude close sharing of the approach.

Note this PR DOES have the impact of adding translate links to 2 localisable
relationship type fields that did not currently have them - I think this is a good thing?

Example of how to enable custom fields for RelationshipType in an extension.
```
      civicrm_api3('OptionValue', 'create', [
        'option_group_id' => 'cg_extend_objects',
        'name' => 'civicrm_relationship_type',
        'label' => ts('Relationship Type'),
        'value' => 'RelationshipType',
      ]);
```
eileenmcnaughton added a commit to eileenmcnaughton/civicrm-core that referenced this pull request May 14, 2018
…nsion.

This is an effort at a way to genericise core forms that basically exist to do crud on an entity. We have a
number of fairly straight forward forms of this type in core and, in order to allow extensions to use
custom fields on a range of entities we should support editing them on these core crud forms.

To add custom data support to an entity we need to
a) add the custom data to the form using CRM_Custom_Form_CustomData::addToForm($this);
b) ensure that the entity is saved using an api call not a BAO call
c) add the custom data block to the tpl file - ie  {include file="CRM/common/customDataBlock.tpl"}

(the above is possible due to previous work to add support & simplify)

In this PR the adding of the customData is done in the EntityFormTrait, the api is previously converted
and the custom data is included by using a generic tpl to support metadata applied to the form.

By using metadata on the form we can also give extension writers a lot more control over what
is on the form as they can add to, alter, or remove the metadata in a php hook. This
is the crux of this issue civicrm#12078 & it likewise is
looking to use a generic field template to add fields based on metadata. A key difference between the 2 prs
is that one uses divs & the other a table & that might preclude close sharing of the approach.

Note this PR DOES have the impact of adding translate links to 2 localisable
relationship type fields that did not currently have them - I think this is a good thing?

Example of how to enable custom fields for RelationshipType in an extension.
```
      civicrm_api3('OptionValue', 'create', [
        'option_group_id' => 'cg_extend_objects',
        'name' => 'civicrm_relationship_type',
        'label' => ts('Relationship Type'),
        'value' => 'RelationshipType',
      ]);
```
eileenmcnaughton added a commit to eileenmcnaughton/civicrm-core that referenced this pull request May 15, 2018
…nsion.

This is an effort at a way to genericise core forms that basically exist to do crud on an entity. We have a
number of fairly straight forward forms of this type in core and, in order to allow extensions to use
custom fields on a range of entities we should support editing them on these core crud forms.

To add custom data support to an entity we need to
a) add the custom data to the form using CRM_Custom_Form_CustomData::addToForm($this);
b) ensure that the entity is saved using an api call not a BAO call
c) add the custom data block to the tpl file - ie  {include file="CRM/common/customDataBlock.tpl"}

(the above is possible due to previous work to add support & simplify)

In this PR the adding of the customData is done in the EntityFormTrait, the api is previously converted
and the custom data is included by using a generic tpl to support metadata applied to the form.

By using metadata on the form we can also give extension writers a lot more control over what
is on the form as they can add to, alter, or remove the metadata in a php hook. This
is the crux of this issue civicrm#12078 & it likewise is
looking to use a generic field template to add fields based on metadata. A key difference between the 2 prs
is that one uses divs & the other a table & that might preclude close sharing of the approach.

Note this PR DOES have the impact of adding translate links to 2 localisable
relationship type fields that did not currently have them - I think this is a good thing?

Example of how to enable custom fields for RelationshipType in an extension.
```
      civicrm_api3('OptionValue', 'create', [
        'option_group_id' => 'cg_extend_objects',
        'name' => 'civicrm_relationship_type',
        'label' => ts('Relationship Type'),
        'value' => 'RelationshipType',
      ]);
```
eileenmcnaughton added a commit to eileenmcnaughton/civicrm-core that referenced this pull request May 15, 2018
…nsion.

This is an effort at a way to genericise core forms that basically exist to do crud on an entity. We have a
number of fairly straight forward forms of this type in core and, in order to allow extensions to use
custom fields on a range of entities we should support editing them on these core crud forms.

To add custom data support to an entity we need to
a) add the custom data to the form using CRM_Custom_Form_CustomData::addToForm($this);
b) ensure that the entity is saved using an api call not a BAO call
c) add the custom data block to the tpl file - ie  {include file="CRM/common/customDataBlock.tpl"}

(the above is possible due to previous work to add support & simplify)

In this PR the adding of the customData is done in the EntityFormTrait, the api is previously converted
and the custom data is included by using a generic tpl to support metadata applied to the form.

By using metadata on the form we can also give extension writers a lot more control over what
is on the form as they can add to, alter, or remove the metadata in a php hook. This
is the crux of this issue civicrm#12078 & it likewise is
looking to use a generic field template to add fields based on metadata. A key difference between the 2 prs
is that one uses divs & the other a table & that might preclude close sharing of the approach.

Note this PR DOES have the impact of adding translate links to 2 localisable
relationship type fields that did not currently have them - I think this is a good thing?

Example of how to enable custom fields for RelationshipType in an extension.
```
      civicrm_api3('OptionValue', 'create', [
        'option_group_id' => 'cg_extend_objects',
        'name' => 'civicrm_relationship_type',
        'label' => ts('Relationship Type'),
        'value' => 'RelationshipType',
      ]);
```
eileenmcnaughton added a commit to eileenmcnaughton/civicrm-core that referenced this pull request May 15, 2018
…nsion.

This is an effort at a way to genericise core forms that basically exist to do crud on an entity. We have a
number of fairly straight forward forms of this type in core and, in order to allow extensions to use
custom fields on a range of entities we should support editing them on these core crud forms.

To add custom data support to an entity we need to
a) add the custom data to the form using CRM_Custom_Form_CustomData::addToForm($this);
b) ensure that the entity is saved using an api call not a BAO call
c) add the custom data block to the tpl file - ie  {include file="CRM/common/customDataBlock.tpl"}

(the above is possible due to previous work to add support & simplify)

In this PR the adding of the customData is done in the EntityFormTrait, the api is previously converted
and the custom data is included by using a generic tpl to support metadata applied to the form.

By using metadata on the form we can also give extension writers a lot more control over what
is on the form as they can add to, alter, or remove the metadata in a php hook. This
is the crux of this issue civicrm#12078 & it likewise is
looking to use a generic field template to add fields based on metadata. A key difference between the 2 prs
is that one uses divs & the other a table & that might preclude close sharing of the approach.

Note this PR DOES have the impact of adding translate links to 2 localisable
relationship type fields that did not currently have them - I think this is a good thing?

Example of how to enable custom fields for RelationshipType in an extension.
```
      civicrm_api3('OptionValue', 'create', [
        'option_group_id' => 'cg_extend_objects',
        'name' => 'civicrm_relationship_type',
        'label' => ts('Relationship Type'),
        'value' => 'RelationshipType',
      ]);
```
@JoeMurray
Copy link
Contributor

@Monish from perspective of wanting fields to define labels in html in way good for supporting a11y, do you see any issues here or have suggestions to make? Ideally these changes when rolled out gradually across the code base would eliminate/greatly reduce the special case handling we are seeing is needed on many fields/forms.

@reneolivo
Copy link
Contributor

Hello @eileenmcnaughton, I cherry-picked your changes, but had to do some fixes too. Here are some pointers:

  • The field specs are much, much, better so we should go that way.
  • I'm not sure why the previous modifier didn't work for you, does $smarty->template_exists(...) work for you?
  • I'm not sure how to add a modifier to the package repo and see the changes locally. I found a Smarty/plugins/ in core, is it ok to place the modifier there? If not, please guide me on how to to test the modifier when used from the package repo.
  • Can we try to use $smarty->template_exists($templatePath) instead of:
$templatePath = __DIR__ . '/../../../../templates/' . $template;

return is_file($templatePath);

I don't feel __DIR__ is safe enough.

  • I changed the format for the template's help block a bit since it was a really long line.
  • There was a mismatched [] on the field specs.
  • email_on_hold was placed inside preferred_communication_method because it seems they are part of the same search field, see how they are defined in the same column: https://github.com/compucorp/civicrm-core/blob/master/templates/CRM/Contact/Form/Search/Criteria/Basic.tpl#L151-L156
    This is the best solution I could think of, since it doesn't seem one can be displayed without the other. Please let me know if you have a better solution and I can implement it.
  • isTagset I think you may be right, we need to investigate this a bit more. Do you know how to activate this field so I can determine where to best place it?

@eileenmcnaughton
Copy link
Contributor

@reneolivo did you read this? https://lab.civicrm.org/dev/core/issues/115

When I worked through #12128 to achieve a similar outcome I would up preferring to define the tpl rather than use the file_exists

 'weird_looking_field' => [
     'name' => 'weird_looking_field', 
     'template' => CRM/Member/Form/MembershipType/weird_looking_field.tpl',

seems more flexible for overwriting?

@reneolivo
Copy link
Contributor

@eileenmcnaughton I'll have a look at the issue, I didn't get to see it, seems I'll need a bit of time to catch up to the thread. But I rather use convention over configuration: if we manage to make this field extensible it's one less field to configure.

I'll catch up to issue's thread and also run some tests and let you know.

@reneolivo
Copy link
Contributor

@eileenmcnaughton I have seen the comments on the issue and your other PR as well. I approve of the changes, even the template field.

I'll wait until your PR gets approved before starting to change this one in case something changes from the spec so we don't duplicate efforts before everything is settled.

Is that ok?

@eileenmcnaughton
Copy link
Contributor

@reneolivo thanks

@reneolivo reneolivo force-pushed the 101-conditionals-in-search-form branch 2 times, most recently from ebb4b2d to dbcbae0 Compare June 6, 2018 23:56
@reneolivo
Copy link
Contributor

@eileenmcnaughton I have implemented the following changes:

@eileenmcnaughton
Copy link
Contributor

The css seems a bit wrong now
screenshot 2018-06-07 19 11 52

@eileenmcnaughton
Copy link
Contributor

Also - either as this or as a follow up I think we should extract a form template - i.e we have this one

https://github.com/civicrm/civicrm-core/blob/master/templates/CRM/Core/Form/Field.tpl

but it uses not Div so perhaps

CRM/Core/Form/FieldDiv.tpl

too

@eileenmcnaughton
Copy link
Contributor

@reneolivo I started an issue to document the fields format over here https://github.com/civicrm/civicrm-dev-docs/issues/534

@eileenmcnaughton
Copy link
Contributor

Actually css might be OK - might have been cached

@eileenmcnaughton
Copy link
Contributor

eileenmcnaughton commented Jun 7, 2018

OK - I have found one issue but I feel like I looked fairly carefully & we are down to the following

  1. If an activity tag type is defined then the 'Include tags used for Activities' doesn't show
  2. I don't think we need an override for this field

Before
screenshot 2018-06-07 19 42 30

After
screenshot 2018-06-07 19 43 45

@reneolivo reneolivo force-pushed the 101-conditionals-in-search-form branch 2 times, most recently from 2a4718f to 1028e76 Compare June 8, 2018 09:50
@reneolivo
Copy link
Contributor

@eileenmcnaughton I worked on your feedback. Here are the changes:

advanced search hr17 9100 1

  • The fields with custom templates were stripped of their wrapper div.search-field tag. That tag is now handled at the Basic.tpl level.
  • Implemented the field description for phone_numeric and uf_user which made their custom template fields unnecessary.
  • all_tag_types ("Include tags used for Activities") is displayed if the field is included in the basic search form.

Also, the tag set field is displayed when there is a tag set related to contacts. Since this field was previously displayed by checking if the template has the $isTagset variable set to true and it's not a field per se, a is_custom was added to the field spec of this field in order to display it. If this property is set to true, it will display the template for the field even if there are no form fields associated to the spec.

The field is displayed in a new row as opposed to how it was displayed before, in a 4th column, because the CSS grid was already fixed to 3 columns.

It's important to note that CSS grids were chosen over the previous table structure because the former is more flexible when removing a particular field. For example, if one would remove all the cells in a row, the row would still exist, occupying an empty space.

@eileenmcnaughton
Copy link
Contributor

I think we are there on this. I've had a few looks at it & as of yesterday the items you have now addressed were the only outstanding ones. Let's merge -on-pass

@eileenmcnaughton
Copy link
Contributor

@reneolivo jenkins failed on (one) style warning

By implementing the field spec pattern as detailed in dev/core#115 extension developers have more control over
 what is displayed in this template without needing to resort to
 overriding it
@eileenmcnaughton
Copy link
Contributor

bang!

@reneolivo
Copy link
Contributor

First try!
first-try

reneolivo pushed a commit to compucorp/civicrm-core that referenced this pull request Jun 11, 2018
reneolivo pushed a commit to compucorp/civicrm-core that referenced this pull request Jun 12, 2018
davialexandre pushed a commit to compucorp/civicrm-core that referenced this pull request Jun 26, 2018
davialexandre pushed a commit to compucorp/civicrm-core that referenced this pull request Jul 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants