Possible to search for a number in the range with views exposed filters?

Created on 16 October 2022, over 2 years ago
Updated 19 August 2024, 8 months ago

Problem/Motivation

As the title describes it - when you enter a range like 100 - 125, is it possible to use views exposed filters to search for 110 or 110 - 115?

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

💬 Support request
Status

Active

Version

1.4

Component

Documentation

Created by

🇦🇹Austria gr4phic3r

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇨🇦Canada noah

    I had a need for the functionality requested here, so I took a run at making it work via hook_views_query_alter()—I went this route rather than trying to create a patch because I think there are some UX and other decisions that would need to be made here that are better left to the maintainers (if they decide to pursue this). In the meantime, the following query adjustment is working for me for a range filter called "price", so I'm putting it here in case it's useful to anyone else:

    /**
     * Implements hook_views_query_alter().
     *
     * Revise the price range filter to compare range to range, rather than value
     * to range.
     */
    function MODULE_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
      if (($exposed_input = $view?->getExposedInput())
        && (isset($exposed_input['price']))
        && (!empty($exposed_input['price']))
        && ($filters = $view?->getDisplay()?->getOption('filters'))
        && (isset($filters['field_price_range']['group_info']['group_items']))
        && ($option_data = $filters['field_price_range']['group_info']['group_items'])) {
        // The Views filter only provides the floor for each filter range; establish
        // ceilings by loading all the floors, sorting by floor (in case they aren't
        // in order), then using each floor to establish the ceiling of the previous
        // range (if one exists). This won't provide a ceiling for the final range,
        // but fortunately in this case the final range doesn't need a ceiling.
        uasort($option_data, fn($a, $b) => $a['value'] <=> $b['value']);
        $options = [];
        foreach ($option_data as $key => $option) {
          if (isset($option['value'])) {
            $options[$key] = ['floor' => $option['value']];
            if (isset($previous_key) && ($previous_key)) {
              $options[$previous_key]['ceiling'] = $option['value'];
            }
            $previous_key = $key;
          }
        }
        if (!empty($options)) {
          // Use the existing range condition to determine the group delta and the
          // fields, then unset the condition so that it can be replaced with a
          // range to range comparison.
          foreach ($query?->where as $group_delta => $condition_group) {
            $fields = [];
            foreach ($condition_group['conditions'] as $delta => $condition) {
              if (isset($condition['field']) && ($condition['field'] instanceof Condition)) {
                // The condition is an instance of
                // “Drupal\Core\Database\Query\Condition”, and I can't figure out
                // the “right” way to access it, but casting to array makes the
                // elements accessible so that the “to” and “from” field names can
                // be extracted.
                $condition_field = (array) $condition['field'];
                foreach ($condition_field as $field_name => $field_data) {
                  if (strpos($field_name, 'conditions') !== FALSE) {
                    if (!isset($fields['from']) && (isset($field_data[0]['field']))) {
                      $fields['from'] = $field_data[0]['field'];
                    }
                    if (!isset($fields['to']) && (isset($field_data[1]['field']))) {
                      $fields['to'] = $field_data[1]['field'];
                    }
                  }
                }
                unset($query->where[$group_delta]['conditions'][$delta]);
              }
            }
            // If the fields are available, add new conditions to the current group.
            if (count($fields) == 2) {
              foreach ($exposed_input['price'] as $price_range_index) {
                if (isset($options[$price_range_index])) {
                  $query_snippets = [];
                  if (isset($options[$price_range_index]['floor'])) {
                    $query_snippets[] = "{$fields['to']} >= {$options[$price_range_index]['floor']}";
                  }
                  if (isset($options[$price_range_index]['ceiling'])) {
                    $query_snippets[] = "{$fields['from']} < {$options[$price_range_index]['ceiling']}";
                  }
                  if (!empty($query_snippets)) {
                    $query->addWhereExpression($group_delta, implode(' AND ', $query_snippets));
                  }
                }
              }
            }
          }
        }
      }
    }
    
Production build 0.71.5 2024