Adding optgroup
elements to a "has taxonomy term (with depth)" exposed filter on a Drupal 9/10 view (for sharing on Drupal issue #2384203).
Screenshot of what the select with optgroup
looks like on our site, edited for length:
Select with optgroup
HTML:
<select data-drupal-selector="edit-unique-category-depth-filter" id="edit-unique-category-depth-filter" name="unique_category_depth_filter" class="form-select">
<option value="All" selected="selected">- Any -</option>
<optgroup label="Approvals">
<option value="1352">All in Approvals</option>
<option value="1354">FPARs</option>
<option value="1355">Municipal Reviews, Approvals and Permitting</option>
</optgroup>
<optgroup label="Procurement">
<option value="1330">All in Procurement</option>
<option value="1331">Consultant Selection</option>
<option value="1332">Vendor selection</option>
<option value="1333">Contractor selection</option>
<option value="1334">Contracts</option></optgroup>
<optgroup label="Design">
<option value="1353">All in Design</option>
<option value="1356">Submissions & Reviews</option>
<option value="1357">Process</option>
<option value="1358">HazMat</option>
<option value="1359">Site Design</option>
</optgroup>
</select>
PHP code to make it happen -- I put it in our .theme
file, but it
could certainly go in a custom module. (And no, we didn't check for the view
name/display -- we could've, maybe we should've, but we didn't :) )
<?php
/**
* Resources view > Document Category filter:
* - Add "optgroup" elements for each parent term.
* - Add the parent term as the first selectable option in each optgroup.
*/
function mysite_form_views_exposed_form_alter(&$form, $form_state, $form_id) {
$filter_keys = array_filter(array_keys($form), fn($v) => str_starts_with($v, 'unique_category_depth_filter'));
if (empty($filter_keys)) {
return;
}
foreach ($filter_keys as $filter_key) {
switch ($filter_key) {
case 'unique_category_depth_filter':
$current_options = $form[$filter_key]['#options'];
$current_parent = "";
$option_any = array_shift($current_options);
$new_options = [];
$new_options['All'] = $option_any;
foreach ($current_options as $option) {
$option_array = $option->option;
$tax_id = array_key_first($option_array);
$option_value = array_values($option_array)[0];
if (str_starts_with($option_value, '-')) {
$prefix = '-';
if (substr($option_value, 0, strlen($prefix)) == $prefix) {
$option_value = substr($option_value, strlen($prefix));
}
$new_options[$current_parent][$tax_id] = $option_value;
}
// or if it's a parent option
else {
$current_parent = $option_value;
$new_options[$option_value] = [];
$new_options[$option_value][$tax_id] = 'All in ' . $option_value;
}
}
unset($form[$filter_key]['#size']);
$form[$filter_key]['#type'] = 'select';
$form[$filter_key]['#options'] = $new_options;
// yes, that's it -- Drupal takes it from here.
break;
}
}
}
?>