Support

Account

Home Forums Backend Issues (wp-admin) How can I dynamically filter the choices in a taxonomy select?

Solving

How can I dynamically filter the choices in a taxonomy select?

  • More specifically – I have multi-select taxonomy field A. When one or more values are selected in A, I want to restrict the choices available in taxonomy field B.

    Taxonomy A contains an ACF taxonomy multi-select for terms from Taxonomy B, which defines the choices that should be available in field B in the post editor.

    So for example:
    * in term X of Taxonomy-A, the user has already selected 5 terms from Taxonomy B.
    * in term Y of Taxonomy-A, the user has already selected 7 terms from Taxonomy B.
    * 3 terms are common to X and Y
    * in the post editor, the user selects X and Y in Taxonomy-A. Now in Taxonomy B field, there should only be 3 terms available, from which the user can select 1.

    FWIW, this is the logic I use to get the allowable B terms (called by both ajax and regular load_field_choice functions):

    
    private function get_tax_b_choices($array_of_tax_a_ids) {
    	$array_of_array_of_b_choices = array();
    	$b_choices = array();
    
    	if(!empty($array_of_tax_a_ids)) {
    		foreach ($array_of_tax_a_ids as $tax_a_id) {
    
    			$choices = array(); 
    			// a_tax_b is the ACF taxonomy multi-select field for allowable b terms, which is in taxonomy a
    			$array_of_tax_b_ids = get_field('a_tax_b', 'term_' . $tax_a_id); 
    			
    			foreach ($array_of_tax_b_ids as $tax_b_id) {
    			 	$tax_b_name = get_term($tax_b_id)->name; // get b name from id
    			 	$choices[$tax_b_id] = $tax_b_name;
    			}
    			
    			$array_of_array_of_b_choices[$tax_a_id] = $choices;
    			$b_choices = $choices; // this only gets used if exactly 1 a term is selected
    		}
    	}
    
    	if(count($array_of_array_of_b_choices) >1) { // only do this if there are multiple a terms to be intersected
    		// find which b choices are common to all of the selected a (intersection)
    		$b_choices = call_user_func_array('array_intersect', $array_of_array_of_b_choices);
    	}
    
    	return $b_choices;
    }
    

    BTW I’m aware of https://github.com/Hube2/acf-dynamic-ajax-select-example, and based much of my code on that.

    I think my main issue is that I can’t figure out the correct jquery selector and html to push onto the select2.

  • 🙂

    Dealing with getting values from and updating values in select2 fields is not really something I’ve tackled. I haven’t quite figured that out yet and I’m always in too much of a hurry when I need it on a project and generally find ways to work around it.

    To be honest, I’m not sure that you can alter the values available in the second select2 field. The reason for this is that ACF is doing it’s own AJAX request to get the values that appear there, so in order to substitute your own values you’d probably need to figure out how to make ACF not update the field in the first place, something that I’m not sure is even possible in the first place. Once you do that you’d probably need to look into how to update the select2 choices in the field by calling a select2 method.

    I recently had a similar situation where I needed to provide 2 fields for a client that allowed selecting a “Parent” term and a second field to select a “Child” term. This was required to restrict the choices basically so that the client could not screw things up. What I ended up doing was using 2 regular select fields. The first field only shows top level terms in the taxonomy and the second one shows only child terms of the top level term selected. Both fields are required so that they must select a parent and a child term.

    Generally, whenever I’m doing complex things like you found in my repo I fall back to using basic fields and coding all the logic myself rather than try to manipulate the ACF select2 fields.

    Just a heads up though. The developer is currently looking into adding filters that allow you to manipulate these fields from the JS side https://github.com/AdvancedCustomFields/acf/issues/4

  • Thanks for the reply. At least I know I hadn’t missed something basic.

    I can live with basic radio options for field B, and I suppose I can figure out for that myself the html to be sent from ajax. One issue arises from that though. On initial page load, there would be hundreds of field B options, which would badly break the page layout. Any thoughts on how I can restrict the vertical height of the radio options list? Is there a way to wrap the field in an iframe, for example?

  • Maybe some code from my current project will help. I don’t dynamically generate the content of the second field in php when the page is loaded. Since you’re familiar with my other examples.

    This is a cut down version of the JS file. I have 2 actions, one to initialize the sub category field and one when the category field changes. The init field basically just hides the sub category field if there is no selection in the category field. There is a line near the bottom where I trigger the init action when the page is loaded.

    
    jQuery(document).ready(function($) {
      if (typeof(acf) == 'undefined') {
        return;
      }
      var CMACF = acf.ajax.extend({
        events: {
          
          // initialize product sub category field
          'init [data-key="field_59dcc174f734c"] .acf-input select': 'init_product_sub_category',
          
          // change sub categories on category select
          'change [data-key="field_59dcc100f734b"] .acf-input select': 'change_product_category',
        }, // end events
        
        change_product_category: function(e) {
          var category = e.$el.val();
          
          //console.log(category);
          
          // clear current sub category
          $('[data-key="field_59dcc174f734c"] .acf-input select').val('');
          
          var action = 'get_product_sub_categories';
          
          var self = this;
          var data = this.o;
          
          data.action = 'cm_get_product_sub_categories';
          data.category = category;
          
          data.exists = [];
          
          this.request = $.ajax({
            url: acf.get('ajaxurl'),
            data: acf.prepare_for_ajax(data),
            type: 'post',
            dataType: 'json',
            success: function(json) {
              console.log(json);
              var $select = $('[data-key="field_59dcc174f734c"] .acf-input select');
              if (!json || json.length == 1) {
                // hide sub category select
                $select.closest('.acf-field').css('display', 'none');
                return;
              }
              // clear all selections from sub category select and replace with new values
              $select.empty();
              for (i=0; i<json.length; i++) {
                $select.append('<option value="'+json[i]['value']+'">'+json[i]['label']+'</option>');
              }
              // make sure the field is visible
              $select.closest('.acf-field').css('display', 'block');
            },
            error: function(jqXHR, textStatus, error) {
              console.log(jqXHR);
              console.log(textStatus);
              console.log(error);
            }
          }); // end request
          
        }, // end change_product_categoty
        
        init_product_sub_category: function(e) {
          // if catetory has a selection and that category has children show field
          // otherwise hide the field
          var category = $('[data-key="field_59dcc100f734b"] .acf-input select').val();
          //console.log(category);
          if (category == '') {
            e.$el.closest('.acf-field').css('display', 'none');
            e.$el.val('');
          }
          
          var action = 'get_product_sub_categories';
          
          var self = this;
          var data = this.o;
          
          data.action = 'cm_get_product_sub_categories';
          data.category = category;
          
          data.exists = [];
          
          this.request = $.ajax({
            url: acf.get('ajaxurl'),
            data: acf.prepare_for_ajax(data),
            type: 'post',
            dataType: 'json',
            success: function(json) {
              //console.log(json);
              if (!json || json.length == 1) {
                e.$el.closest('.acf-field').css('display', 'none');
                e.$el.val('');
                return;
              }
            },
            error: function(jqXHR, textStatus, error) {
              console.log(jqXHR);
              console.log(textStatus);
              console.log(error);
            }
          }); // end request
          
        }, // end init_product_sub_category
        
      });
      
      $('[data-key="field_59dcc174f734c"] .acf-input select').trigger('init');
      
    });
    

    This is the function that dynamically loads the choices of the sub category in php, as you can see by this, if nothing has been saved for the top level category field it returns and empty array for choices.

    
    function load_product_sub_category_field($field) {
      global $post;
      if ($post && isset($post->ID) && get_post_type($post->ID) == 'acf-field-group') {
        return $field;
      }
      $post_id = $post->ID;
      $category = intval(get_field('field_59dcc100f734b', $post_id));
      $choices = array();
      $terms = array();
      if ($category) {
        $args = array(
          'taxonomy' => 'product-category',
          'parent' => $category,
          'hide_empty' => false
        );
        $terms = get_terms($args);
      }
      if ($terms && !is_wp_error($terms)) {
        foreach ($terms as $term) {
          $choices[$term->term_id] = $term->name;
        }
      }
      $field['choices'] = $choices;
      return $field;
    }
    

    We can do this because when the post is saved ACF does not validate the value submitted for the field against the choices of the field. You’ll also notice that I don’t do this on the field group editor page. When I created this select field what I entered for choices is this will be dynamically generated. So choices are only added to this field if
    1) The post has already been saved and there is a value in the field this depends on
    OR
    2) When something is selected for the category and the field is dynamically populated using AJAX

    And finally, this is the function called in the AJAX request

    
    function get_product_sub_categories() {
      
      if (!wp_verify_nonce($_POST['nonce'], 'acf_nonce')) {
        echo json_encode(false);
        exit;
      }
      
      $check_fields = array(
        'category'
      );
      
      foreach ($check_fields as $index) {
        if (!isset($_POST[$index])) {
          echo json_encode(false);
          exit;
        }
      }
      
      $choices = array(array('value' => '', 'label' => '- Select -'));
      if (!$_POST['category']) {
        echo json_encode($choices);
        exit;
      }
      
      $args = array(
        'taxonomy' => 'product-category',
        'parent' => intval($_POST['category']),
        'hide_empty' => false
      );
      $terms = get_terms($args);
      
      if ($terms && !is_wp_error($terms)) {
        foreach ($terms as $term) {
          $choices[] = array('value' => $term->term_id, 'label' => $term->name);
        }
      }
      
      echo json_encode($choices);
      exit;
      
    }
    
  • This reply has been marked as private.
  • This reply has been marked as private.
Viewing 6 posts - 1 through 6 (of 6 total)

You must be logged in to reply to this topic.