2

I have two drop downs. The first one contains list of parent taxonomies from a custom post type. And the second one contains all the sub-taxonomies at a time. But I'm trying to display only those sub-taxonomies of selected parent taxonomy (not all the sub-taxonomies from other parent taxonomies). For example, if I select a parent taxonomy from the first dropdown, then second dropdown should only display the sub-taxonomies of it. I'm not finding any php logic to achieve this goal. I'm displaying these two dropdowns on 'Edit Profile' page. Here is my code on functions.php

<?php
function slcustom_user_profile_fields($user) { ?>
<h1 id="temppp">Select a parent taxonomy</h1>
<select name="parent_category" id="parent_category">
  <?php
    global $wpdb;
    $parentCat = $wpdb->get_results( "SELECT name FROM wp_terms WHERE term_id IN (SELECT term_id FROM wp_term_taxonomy WHERE taxonomy = 'project_category' AND parent = 0)" );
    foreach ( $parentCat as $cat ) { ?>
      <option value="<?php echo esc_attr( $cat->name ) ?>"<?php echo selected( $user->parent_category, $cat->name ); ?>><?php echo $cat->name; ?></option>
    <?php }
  ?>
</select>

<p>Select a child taxonomy</p>
<select name="child_category" id="child_category">
  <?php 
//trying to bring this $termName value from JS
if(isset($_POST['selectedParentName'])) {
  $termName = isset($_POST['selectedParentName']);
}

$termId = get_term_by('name', $termName, 'project_category');
$selectedTermId = $termId->term_id;

  $id = $selectedTermId;
    $childCats = $wpdb->get_results( "SELECT name FROM wp_terms WHERE term_id IN (SELECT term_id FROM wp_term_taxonomy WHERE taxonomy = 'project_category' AND parent = ".$id." )" );
    foreach ($childCats as $childCat) { ?>
      <option value="<?php echo esc_attr( $childCat->name ) ?>"<?php echo selected( $user->child_category, $childCat->name ); ?>><?php echo $childCat->name; ?></option>
    <?php }
  ?>
</select>
<?php
}
add_action('show_user_profile', 'slcustom_user_profile_fields');
add_action('edit_user_profile', 'slcustom_user_profile_fields');


function save_custom_user_profile_fields($user_id) {
    if( current_user_can('edit_user', $user_id) ) {
        update_user_meta($user_id, 'parent_category', $_POST['parent_category']);
        update_user_meta($user_id, 'child_category', $_POST['child_category']);
    }
}
add_action('personal_options_update', 'save_custom_user_profile_fields');
add_action('edit_user_profile_update', 'save_custom_user_profile_fields');

script.js

$('select#parent_category').on('change', function() {
  var selectedParentName = this.value; //it tracks changes dropdown value
  $.ajax({
    type: 'POST',
    url: 'http://localhost:3000/wp-content/themes/mytheme/functions.php',
    data: { selectedParentName : selectedParentName }, //trying to send this 'selectedParentName' to functions.php
    success: function(data) {
       var sendThisDataToPHP = selectedParentName;
    }
  });
});

Updated Feb 19, 2019: script.js added and few lines on functions.php to receiving JS variable.

4
+50

Here's a fully functional snippet for doing this.

  • I added the correct WordPress table stylings
  • This requires adding my smyles_get_taxonomy_hierarchy to add children of taxonomy to the term object
  • This hides the child dropdown if no children exist
  • This handles everything (and saves) based on TERM ID not the NAME .. you should always use term ID as if you decide to change the wording/slug later on you will have all kinds of issues

example

Seems code syntax highlighter on here can't handle mix of HTML/JS/PHP, so here's a GitHub Gist of working code: https://gist.github.com/tripflex/527dd82db1d1f3bf82f761486fcc3303

Breakdown:

You first need to include my function to generate taxonomy array with children in that term object:

if ( ! function_exists( 'smyles_get_taxonomy_hierarchy' ) ) {
    /**
     * Recursively get taxonomy and its children
     *
     * @param string $taxonomy
     * @param int    $parent Parent term ID (0 for top level)
     * @param array  $args   Array of arguments to pass to get_terms (to override default)
     *
     * @return array
     */
    function smyles_get_taxonomy_hierarchy( $taxonomy, $parent = 0, $args = array( 'hide_empty' => false ) ) {

        $defaults = array(
            'parent'     => $parent,
            'hide_empty' => false
        );
        $r        = wp_parse_args( $args, $defaults );
        // get all direct decendants of the $parent
        $terms = get_terms( $taxonomy, $r );
        // prepare a new array.  these are the children of $parent
        // we'll ultimately copy all the $terms into this new array, but only after they
        // find their own children
        $children = array();
        // go through all the direct decendants of $parent, and gather their children
        foreach ( $terms as $term ) {
            // recurse to get the direct decendants of "this" term
            $term->children = smyles_get_taxonomy_hierarchy( $taxonomy, $term->term_id );
            // add the term to our new array
            $children[ $term->term_id ] = $term;
        }

        // send the results back to the caller
        return $children;
    }
}

jQuery/JavaScript code handling:

jQuery( function($){
    // slcustom_categories var should be available here
    $('#parent_category').change( function(e){
        var child_cat_select = $( '#child_category' );
        var term_id = $(this).val();

        console.log( 'Parent Category Changed', term_id );

        // Remove any existing
        child_cat_select.find( 'option' ).remove();

        // Loop through children and add to children dropdown
        if( slcustom_categories && slcustom_categories[ term_id ] && slcustom_categories[ term_id ]['children'] ){
            console.log( 'Adding Children', slcustom_categories[ term_id ]['children'] );
            $.each( slcustom_categories[term_id]['children'], function( i, v ){
                console.log( 'Adding Child: ', v );
                child_cat_select.append( '<option value="' + v['term_id'] + '">' + v[ 'name' ] + '</option>');
            });

            // Show if child cats
            $( '#child_category_row' ).show();
        } else {
            // Hide if no child cats
            $( '#child_category_row' ).hide();
        }
    });

    // Trigger change on initial page load to load child categories
    $('#parent_category').change();
});

Function to output fields:

function slcustom_user_profile_fields( $user ){

    $categories = smyles_get_taxonomy_hierarchy( 'project_category' );
    $parent_category = $user->parent_category;
    $child_category = $user->child_category;
//  $parent_category = 52; // used for testing
//  $child_category = 82; // used for testing
    $parent_has_children = ! empty( $parent_category ) && $categories[ $parent_category ] && ! empty( $categories[ $parent_category ]->children );

    // Creative way to use wp_localize_script which creates a JS variable from array
    // You should actually change this to load your JavaScript file and move JS below to that file
    wp_register_script( 'slcustom_user_profile_fields', '' );
    wp_localize_script( 'slcustom_user_profile_fields', 'slcustom_categories', $categories );
    wp_enqueue_script( 'slcustom_user_profile_fields' );
    ?>
    <h1 id="temppp">Select a parent taxonomy</h1>
    <table class="form-table">
        <tbody>
            <tr>
                <th>
                    Parent
                </th>
                <td>
                    <select name="parent_category" id="parent_category">
                    <?php
                        foreach( (array) $categories as $term_id => $cat ){
                            ?>
                            <option value="<?php echo esc_attr( $term_id ) ?>"<?php echo selected( $parent_category, $term_id ); ?>><?php echo $cat->name; ?></option>
                            <?php
                        }
                    ?>
                    </select>
                </td>
            </tr>
            <tr id="child_category_row" style="<?php if( ! $parent_has_children ){ echo 'display: none;'; }?>">
                <th>
                    Child
                </th>
                <td>
                    <select name="child_category" id="child_category">
                        <?php
                            if( $parent_has_children ){
                                foreach( (array) $categories[$parent_category]->children as $c_term_id => $child ){
                                    ?>
                                    <option value="<?php echo esc_attr( $c_term_id ) ?>"<?php echo selected( $child_category, $c_term_id ); ?>><?php echo $child->name; ?></option>
                                    <?php
                                }
                            }
                        ?>
                    </select>
                </td>
            </tr>
        </tbody>
    </table>
    <?php
}
  • nice example ... – user5377037 Feb 20 at 15:34
  • Thanks but the children aren't populating based on selected parent. It only keeps track of changes of selected parent which I already have. I'm trying to populating the second drop-down with with selected parent item. – shihab Feb 20 at 17:10
  • @Shihab you're sure you have your taxonomies setup correctly? As in they are hierarchal and are actual child taxonomies? Do you mean you're trying to populate the second dropdown with the children of the selected parent taxonomy ... right? Can you edit your main post and add a screenshot of your taxonomies so we can make sure they are setup correctly. – sMyles Feb 20 at 18:45
  • Could you please check this screenshot on this link – shihab Feb 20 at 19:02
  • 1
    @Shihab glad to hear we got this resolved :) – sMyles Feb 21 at 16:38
1

You can use a simple foreach loop.

Let say I have a custom taxonomy "books"

This is how I get all the parents terms:

$terms = get_terms(array('taxonomy' => 'books', 'hide_empty' => false ));
$parents = array();
foreach ($terms as $item) {
    if($item->parent===0){
        $parents[] = $item;
    }
}
print_r($parents); //Show all the parent objects

On ajax call, send the parent id, to get the list of children objects, like this:

$parent = $_POST['parent'];
$terms = get_terms(array('taxonomy' => 'books', 'hide_empty' => false ));
$children = array();
foreach ($terms as $item) {
    if($item->parent === $parent){
        $children[] = $item;
    }
}
print_r($children); //show all children objects
1

You could achieve that by using js / jQuery. There might be more ways to it, but two comes into my mind.

  1. Watch for changes on the parent select and populate the child select options with ajax based on the selected parent option.
  2. Watch for changes on the parent select and hide the unrelated options from the child select based on the selected parent option.

EDIT 19.2.2019

add_action( 'wp_ajax_my_action', 'my_action' );
function my_action() {

  // check if user is logged in
  // is ajax request nonce valid
  // other stuff to make sure we can trust this request

  $selected_aprent_tax = strip_tags($_POST['parent_cat']);

  $tax_query = new WP_Tax_Query(array(
    'taxonomy'  =>  'project_category',
    'parent'    =>  $selected_aprent_tax,
  ));

  if ( $tax_query->terms ) {
    wp_send_json_success( $tax_query->terms );
  } else {
    wp_send_json( array() );
    wp_die();
  }

}

Then use my_action as the action parameter in your ajax request to trigger the function above. And send the ajax request to admin_url( 'admin-ajax.php' ).

With your jQuery script you would then grab the terms the php returns and do whatever you want with them. E.g. create select options from them.

NB It's quite late as I'm typing this so it's not a perfect example, but a rough one. Please refer to the codex to make the example work in your case, https://codex.wordpress.org/AJAX_in_Plugins

  • Thanks for your kind response. I was trying this plugin link . Was this plugin right for my purpose? – shihab Feb 17 at 11:51
  • If you're using that jQuery plugin, then I think you would need to render, in your php code, separate child select element for each option in your parent select. Then it would probably work so that the child select list with the correct options could be shown. – Antti Koskinen Feb 17 at 12:01
  • I have updated my code to track changes of parent and this portion is working but yet I'm not able to send a variable from Javascript to PHP. If you would take a look at the code will be highly appreciated. – shihab Feb 19 at 11:09
  • Have a look at the ajax documentation on the codex, codex.wordpress.org/AJAX_in_Plugins It describes how you can send data from js to php and back. I'll add and example to my answer – Antti Koskinen Feb 19 at 21:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.