If I have a formatter settings element that uses #states, the form element names that I set there that are standardized in field UI, are different from the form elements that are output when displayed in a Views field handler form. See #1387832-3: Support #states from Field API formatter settings forms:

fieldname
field_contact_phone
field html id
edit-fields-field-contact-phone-settings-edit-form-settings-click-to-edit
field html name
fields[field_contact_phone][settings_edit_form][settings][click_to_edit]
views html id
edit-options-settings-click-to-edit
views html name
options[settings][click_to_edit]

This leads to a bit of a WTF for anyone with a field formatter that uses #states because now site builders cannot see the formatter settings they should be filling in.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Issue tags: +VDC
Dave Reid’s picture

I have a feeling that Views will not be alone in the need for this, so I added a general drupal_rewrite_states_selector() helper function.
Included is a patch that can be used to test this by adding a #states on image formatter's 'Link image to' setting being dependent on the image style being 'medium'.

Dave Reid’s picture

Dave Reid’s picture

It actually would be nice for forms to be able to pass into the field formatter settings form what the 'base' element is named, but I think this is a good solution for now. Tim said some more inline documentation is necessary.

dawehner’s picture

Issue tags: +Needs tests

That's a great idea!

I guess we would also need some test for this new function.

Dave Reid’s picture

Ideas on how to test this or a pointer to an existing test that hits some of the same code would be welcome.

dawehner’s picture

jhedstrom’s picture

Re-rolled the above solution, but would this make more sense in a component somewhere?

dawehner’s picture

Issue tags: +VDC, +Needs tests

I think having this there, for now, is a totally fine place ... All of the states API is still in that place.

Let's readd the tags, @jhedstrom do you mind writing a test?

Status: Needs review » Needs work

The last submitted patch, 8: 1985406-08-views-field-formatter-settings-states.patch, failed testing.

jhedstrom’s picture

This adds a very simple test (taken from Dave Reid's example).

Status: Needs review » Needs work

The last submitted patch, 11: 1985406-10-views-field-formatter-settings-states.patch, failed testing.

jhedstrom’s picture

Status: Needs work » Needs review
FileSize
865 bytes
3.21 KB

The property $field->id no longer exists. I've updated the patch to use $field->getLabel() instead, but am not sure that is the correct change to make.

jhedstrom’s picture

FileSize
839 bytes
872 bytes
3.21 KB

The proper method to use is getName() (in my manual testing anyway). The id() method includes entity and bundle info, which is not present in the markup.

I've included an update to the manual testing patch.

jhedstrom’s picture

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs tests

Cool. Does someone know whether we should backport this to D7 (API addition or just directly in views)?

alexpott’s picture

I think we might be able to leverage this code in FilterPluginBase::buildExposedFiltersGroupForm() there is a load of code after line 988 manipulating states.

dawehner’s picture

@alexpott
So no commit of the current status which already solves a bug? your suggestion is just a task.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/includes/common.inc
    @@ -2021,6 +2021,33 @@ function drupal_process_states(&$elements) {
    +function drupal_rewrite_states_selector(array &$elements, $search, $replace) {
    

    I think this should be a static method on a new FormHelper class.

  2. +++ b/core/includes/common.inc
    @@ -2021,6 +2021,33 @@ function drupal_process_states(&$elements) {
    +  foreach (element_children($elements) as $key) {
    

    Deprecated.

  3. +++ b/core/modules/system/src/Tests/Form/StatesTest.php
    @@ -0,0 +1,37 @@
    +class StatesTest extends DrupalUnitTestBase {
    

    Once the above is done this can be a PHPUnit test.

alexpott’s picture

Also this will fail in the states array is declared like this:

          '#states' => array(
            'visible' => array(
              array(
                ':input[name="menu[type]"]' => array('value' => 'normal'),
              ),
              array(
                ':input[name="menu[type]"]' => array('value' => 'tab'),
              ),
              array(
                ':input[name="menu[type]"]' => array('value' => 'default tab'),
              ),
            ),

From \Drupal\views\Plugin\views\display\Page

See #2356443: Grouped numeric filters can not have a value set for an instance of this bug - and somewhere this function should be used.

jhedstrom’s picture

Status: Needs work » Needs review
FileSize
7.6 KB
5.1 KB

This patch moves the logic to a helper class as suggested, and has been reworked to apply to the example in #20.

I need more insight to the code in FilterPluginBase::buildExposedFiltersGroupForm() before I feel comfortable porting it over to use this new helper method.

alexpott’s picture

Priority: Normal » Major
FileSize
2.87 KB
8.12 KB

Look at all the untested code we can remove! Closing #2356443: Grouped numeric filters can not have a value set in favour of this issue since this adds a tested method to do what we need. I've manually tested too and it works great.

Upgrading to major since grouped views filters can not be set up without it (if you have javascript turned on).

Steps to reproduce:

  1. Add a numeric field to user
  2. Edit the admin/people view
  3. Add a filter for the numeric field
  4. Expose it and group it.

The value, min and max fields are all hidden. What is supposed to happen is that either the value or the min max fields are supposed to appear depending on which operator you have selected.

olli’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,56 @@
    +      foreach ($elements['#states'] as $state => $ids) {
    +        foreach ($ids as $id => $values) {
    +          if (strpos($id, $search) !== FALSE) {
    +            $new_id = str_replace($search, $replace, $id);
    +            $elements['#states'][$state][$new_id] = $values;
    +            unset($elements['#states'][$state][$id]);
    +          }
    +          elseif (is_array($values)) {
    +            foreach ($values as $selector => $value) {
    +              if (strpos($selector, $search) !== FALSE) {
    +                $new_selector = str_replace($search, $replace, $selector);
    +                $values[$new_selector] = $value;
    +                unset($values[$selector]);
    +              }
    +            }
    +            $elements['#states'][$state][$id] = $values;
    +          }
    +        }
    

    The #states can be more than two levels deep.

  2. +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
    @@ -979,39 +980,14 @@ protected function buildExposedFiltersGroupForm(&$form, FormStateInterface $form
    -          if (!empty($this->options['group_info']['group_items'][$item_id]['value'][$children])) {
    -            $row['value'][$children]['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'][$children];
    -          }
    

    Without this default values are not set. The value, min and max fields are all empty when you go back to edit the grouped filter.

alexpott’s picture

re #23

  1. @olli looking at all the examples in https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_... I can't see how this function does not cope with all of them. Can you provide an example?
  2. Yep that needs fixing.
olli’s picture

alexpott’s picture

How is that different from the example I pointed out in #20 and was subsequently fixed?

alexpott’s picture

Oh I see :) - patch incoming.

alexpott’s picture

Status: Needs work » Needs review
FileSize
5.66 KB
9.85 KB

Added https://www.drupal.org/node/1464758 as part of the test and improved rewriteStatesSelector to handle this. Also fixed #23.2

olli’s picture

Great!! Some notes:

  1. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,68 @@
    +/**
    + * Encapsulates the caching of a form and its form state.
    + *
    + * @ingroup form_api
    + */
    +class FormHelper {
    

    "Provides helpers to operate on forms"?

  2. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,68 @@
    +      foreach ($elements['#states'] as $state => $ids) {
    +        static::processStatesArray($elements['#states'][$state], $search, $replace);
    +      }
    

    Unused $ids.

  3. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,68 @@
    +   * @param array $ids
    +   *   States array element.
    +   * @param $search
    +   *   A partial or entire jQuery selector string to replace in #states.
    +   * @param $replace
    +   *   The string to replace all instances of $search with.
    +   */
    +  protected static function processStatesArray(array &$elements, $search, $replace) {
    

    $ids -> $elements

  4. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,68 @@
    +    foreach ($elements as $id => $values) {
    ...
    +          $keys = array_keys($elements);
    +          $index = array_search($id, $keys, TRUE);
    +          $keys[$index] = $new_id;
    +          $elements = array_combine($keys, array_values($elements));
    

    I wonder if it is possible to move these array_*() friends outside the loop.

  5. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,68 @@
    +      if (strpos($id, $search) !== FALSE) {
    +        $new_id = str_replace($search, $replace, $id);
    +        if ($new_id != $id) {
    

    Why the second "if"?

  6. +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
    @@ -979,39 +980,22 @@ protected function buildExposedFiltersGroupForm(&$form, FormStateInterface $form
    +          if (!empty($this->options['group_info']['group_items'][$item_id]['value'][$child])) {
    +            $row['value'][$child]['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'][$child];
               }
    ...
    -          $row['value'][$children]['#title'] = '';
    -
    -          if (!empty($this->options['group_info']['group_items'][$item_id]['value'][$children])) {
    -            $row['value'][$children]['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'][$children];
    -          }
    

    Since we are inside a table, would #title_display = invisible work here?

  7. +++ b/core/tests/Drupal/Tests/Core/Form/FormHelperTest.php
    @@ -0,0 +1,90 @@
    + * Contains \Drupal\system\Tests\Form\StatesTest.
    

    Contains core FormHelperTest.

alexpott’s picture

FileSize
4.27 KB
10.28 KB
  1. Fixed
  2. Well then we'd have to call array_keys - don't see the point
  3. I've changed this to conditions to mirror the language in the docs for drupal_process_states()
  4. Good point - it is for the array_keys but not the array_values since the values can change during a recursive call.
  5. Overly defensive code - removed.
  6. Well changing that is out of scope - but I had removed in the latest patch :( that code is so untested. Will open a followup to test. Hopefully now I have made the new code work exactly like the old code.
  7. Fixed
alexpott’s picture

I've just scanned the whole of core and I think that FilterPluginBase is the only place we're doing this sort of manipulation of the states array so it's definitely good to covert it to the new helper method.

olli’s picture

FileSize
10.33 KB
1 KB

#30.6 I agree, we need to improve that anyway. I've made a small fix in this code to restore your fix to #23.2.

alexpott’s picture

re #32 @olli nice catch - again! We so need tests!

jhedstrom’s picture

Great work on the expanded tests! RTBC IMO.

olli queued 30: 1985406.30.patch for re-testing.

olli queued 32: 1985406.32.patch for re-testing.

The last submitted patch, 30: 1985406.30.patch, failed testing.

olli’s picture

Status: Needs review » Reviewed & tested by the community

I only made a small change in #32 (we didn't have tests for it yet) so I guess I can set this to RTBC.

Opened #2387353: Improve form element labels in views grouped filter for #29.6

catch’s picture

  1. +++ b/core/lib/Drupal/Core/Form/FormHelper.php
    @@ -0,0 +1,70 @@
    +        $conditions = array_combine($keys, array_values($conditions));
    

    Looks like the array_combine() could happen after the foreach has run?

  2. +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
    @@ -979,39 +980,30 @@ protected function buildExposedFiltersGroupForm(&$form, FormStateInterface $form
    +      // is needed because forms are inside a new form and their ids changes.
    

    IDs change?

catch’s picture

Status: Reviewed & tested by the community » Needs review
alexpott’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Ghent DA sprint
FileSize
2.16 KB
10.47 KB
  1. Nice catch @catch! Yep you're correct - fixed
  2. Fixed (this is in HEAD already but we're moving the comment so fixing).
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 22ca1c9 on 8.0.x
    Issue #1985406 by jhedstrom, alexpott, Dave Reid, olli: #states not...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.