Advertisement

How to Use Radio Buttons With Taxonomies

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

WordPress' custom taxonomy features are fantastic, allowing you to organize your posts in various taxonomies, with all the hard work done for you. However, it can also be a bit constraining. When editing your posts, your taxonomy terms have their own metabox, and they appear either as a checkbox list (for hierarchal taxonomies) or as a tag cloud (for non-hierarchal taxonomies). Those are your two choices.

This can present a problem when you want to ensure only one term can be selected for each post. Of course, you could hook into the save_post hook and remove any 'excess' terms, but this is not particularly user-friendly and certainly doesn't provide a great user interface. Sometimes, it would just be more aesthetically desirable to present your taxonomies in a different way. This article will show you how to do just that, and all the code we talk about should be added to the functions.php file in your theme. We'll focus on radio buttons, but you could use any other input method, for example a drop-down menu.


Step 1 Remove the Default Taxonomy Metabox

WordPress automatically produces the taxonomy metabox, so our first job is to remove it so we can produce our own in its place. I shall assume our taxonomy name is 'mytaxonomy' (if you wanted to alter WordPress' tags or categories metabox, you would replace this with 'category' or 'post_tag').

To remove the metabox we will use remove_meta_box, which should be called from inside a function hooked onto the admin_menu. The remove_meta_box accepts three arguments.

  1. ID: this the id attribute given to the div element containing the metabox. Usually this would be 'mytaxonomydiv' for hierarchal taxonomies, or 'tagsdiv-mytaxonomy' for non-hierarchal ones.
  2. Post type: the post type the metabox appears for (e.g. 'post' or 'page' etc). If your metabox appears for several different post types, you will need to call the remove_meta_box function for each one.
  3. Context: Normal, advanced, or side.
add_action( 'admin_menu', 'myprefix_remove_meta_box');
function myprefix_remove_meta_box(){
   remove_meta_box('mytaxonomydiv', 'post', 'normal');
}

Step 2 Add Your Own Metabox

Here we hook onto the aptly named add_meta_boxes hook with a function that will add our metabox. To do that the function will call add_meta_box which takes quite a few arguments, among which are:

  1. ID: Same as the above, give it anything unique.
  2. Title: The title for the metabox.
  3. Callback: The name of the function that will produce our metabox's innards.
  4. Post type: Same as the above. Again you'll need to call this function for each post type separately.
  5. Context: Same as the above.
  6. Priority: The priority within the context where the boxes should show.
 
//Add new taxonomy meta box
 add_action( 'add_meta_boxes', 'myprefix_add_meta_box');
 function myprefix_add_meta_box() {
     add_meta_box( 'mytaxonomy_id', 'My Radio Taxonomy','myprefix_mytaxonomy_metabox','post' ,'side','core');
 }

  function myprefix_mytaxonomy_metabox( $post ) {
     echo 'This is my taxonomy metabox';
  }

Together, the above should remove the default metabox and replace it with your own, which currently does nothing but display the message "This is my taxonomy metabox". The next step is to change the callback function to display what we want.


Step 3 Producing the Radio Buttons

We want our metabox to look and behave as much like the default metaboxes as is possible. Delving into the WordPress core files, you'll find the place where a metabox's insides are produced, here. Our custom function below will mimic the core function, but with some changes to how our terms are displayed.

Let's go through our function a bit at a time. The first bit sets up some of the variables. You only really need to change the $taxonomy variable to match your taxonomy name. Note also the $name variable. We are giving input fields the name tax_input[mytaxonomy]. This the name for the input inside the default metabox. By doing this, WordPress will automatically handle the updating of a post's taxonomy term.

//Set up the taxonomy object and get terms
$taxonomy = 'mytaxonomy';
$tax = get_taxonomy($taxonomy);//This is the taxonomy object

//The name of the form
$name = 'tax_input[' . $taxonomy . ']';

//Get all the terms for this taxonomy
$terms = get_terms($taxonomy,array('hide_empty' => 0));

We'll be wanting the ID of the post's current term (we're expecting only one).

 
$postterms = get_the_terms( $post->ID,$taxonomy );
$current = ($postterms ? array_pop($postterms) : false);
$current = ($current ? $current->term_id : 0);

If you take a look at WordPress' category metabox, you'll notice a tab which will display the 'most used' terms. To reproduce that we'll need the 10 most popular terms. We use the get_terms function again, but this time selecting at most 10 terms and ordered by count (the number of posts that have this taxonomy).

$popular = get_terms( $taxonomy, array( 'orderby' => 'count', 'order' => 'DESC', 'number' => 10, 'hierarchical' => false ) );

Next we want to display the 'All Categories' and 'Most Used' tabs (it's best practise to use the taxonomy labels wherever possible). If you don't want tabs, you can simply remove this bit:

<!-- Display tabs-->
<ul id="<?php echo $taxonomy; ?>-tabs" class="category-tabs">
	<li class="tabs"><a href="#<?php echo $taxonomy; ?>-all" tabindex="3"><?php echo $tax->labels->all_items; ?></a></li>
	<li class="hide-if-no-js"><a href="#<?php echo $taxonomy; ?>-pop" tabindex="3"><?php _e( 'Most Used' ); ?></a></li>
</ul>

Next, we want to set what to display when we are on the 'all categories' tab:

<!-- Display taxonomy terms -->
<div id="<?php echo $taxonomy; ?>-all" class="tabs-panel">
	<ul id="<?php echo $taxonomy; ?>checklist" class="list:<?php echo $taxonomy?> categorychecklist form-no-clear">
		<?php   foreach($terms as $term){
			$id = $taxonomy.'-'.$term->term_id;
			echo "<li id='$id'><label class='selectit'>";
			echo "<input type='radio' id='in-$id' name='{$name}'".checked($current,$term->term_id,false)."value='$term->term_id' />$term->name<br />";
			echo "</label></li>";
		}?>
	</ul>
</div>

This is really just displaying a list inside a div element, and each list element is a radio option. Of course you can simply replace this list with a drop-down menu or anything else you like.

Now we do the same for the 'most used' tab:

<!-- Display popular taxonomy terms -->
<div id="<?php echo $taxonomy; ?>-pop" class="tabs-panel" style="display: none;">
	<ul id="<?php echo $taxonomy; ?>checklist-pop" class="categorychecklist form-no-clear" >
		<?php   foreach($popular as $term){
			$id = 'popular-'.$taxonomy.'-'.$term->term_id;
			echo "<li id='$id'><label class='selectit'>";
			echo "<input type='radio' id='in-$id'".checked($current,$term->term_id,false)."value='$term->term_id' />$term->name<br />";
			echo "</label></li>";
		}?>
	</ul>
</div>

Step 4 Our Complete Callback Function

Piece it together and our complete function is

//Callback to set up the metabox
function myprefix_mytaxonomy_metabox( $post ) {
    //Get taxonomy and terms
    $taxonomy = 'mytaxonomy';

    //Set up the taxonomy object and get terms
    $tax = get_taxonomy($taxonomy);
    $terms = get_terms($taxonomy,array('hide_empty' => 0));

    //Name of the form
    $name = 'tax_input[' . $taxonomy . ']';

    //Get current and popular terms
    $popular = get_terms( $taxonomy, array( 'orderby' => 'count', 'order' => 'DESC', 'number' => 10, 'hierarchical' => false ) );
    $postterms = get_the_terms( $post->ID,$taxonomy );
    $current = ($postterms ? array_pop($postterms) : false);
    $current = ($current ? $current->term_id : 0);
    ?>

    <div id="taxonomy-<?php echo $taxonomy; ?>" class="categorydiv">

        <!-- Display tabs-->
        <ul id="<?php echo $taxonomy; ?>-tabs" class="category-tabs">
            <li class="tabs"><a href="#<?php echo $taxonomy; ?>-all" tabindex="3"><?php echo $tax->labels->all_items; ?></a></li>
            <li class="hide-if-no-js"><a href="#<?php echo $taxonomy; ?>-pop" tabindex="3"><?php _e( 'Most Used' ); ?></a></li>
        </ul>

        <!-- Display taxonomy terms -->
        <div id="<?php echo $taxonomy; ?>-all" class="tabs-panel">
            <ul id="<?php echo $taxonomy; ?>checklist" class="list:<?php echo $taxonomy?> categorychecklist form-no-clear">
                <?php   foreach($terms as $term){
                    $id = $taxonomy.'-'.$term->term_id;
                    echo "<li id='$id'><label class='selectit'>";
                    echo "<input type='radio' id='in-$id' name='{$name}'".checked($current,$term->term_id,false)."value='$term->term_id' />$term->name<br />";
                   echo "</label></li>";
                }?>
           </ul>
        </div>

        <!-- Display popular taxonomy terms -->
        <div id="<?php echo $taxonomy; ?>-pop" class="tabs-panel" style="display: none;">
            <ul id="<?php echo $taxonomy; ?>checklist-pop" class="categorychecklist form-no-clear" >
                <?php   foreach($popular as $term){
                    $id = 'popular-'.$taxonomy.'-'.$term->term_id;
                    echo "<li id='$id'><label class='selectit'>";
                    echo "<input type='radio' id='in-$id'".checked($current,$term->term_id,false)."value='$term->term_id' />$term->name<br />";
                    echo "</label></li>";
                }?>
           </ul>
       </div>

    </div>
    <?php
}

Step 5 A Little JavaScript...

I was careful in my naming of IDs and radio buttons in the callback function. If you try all the above now, you'll find that WordPress automatically handles the updating of post terms. Furthermore, WordPress' javascript automatically handles the tab navigation. There is one slight hiccup. The 'all categories' radio buttons are not in sync with the 'most used'. If you've decided to dispense with the 'most used' tab then you can ignore this section. Otherwise, we just need to add tiny bit of javascript to fix this issue.

We want to add a bit of javascript to the page, so inside our callback function we'll use a hook that fires when javascript is added in the admin. That is, the admin_enqueue_scripts hook. Since we add our function onto this hook inside our callback function, it's only loaded when it's needed. Just add this line at the top of our callback function above:

add_action('admin_enqueue_scripts','myprefix_radiotax_javascript');

When the javascripts are being loaded in the admin page, this will trigger our function. This function does nothing more than register and enqueue our javascript, which we want to load in the footer:

function myprefix_radiotax_javascript(){
	wp_register_script( 'radiotax', get_template_directory_uri() . '/js/radiotax.js', array('jquery'), null, true ); // We specify true here to tell WordPress this script needs to be loaded in the footer
	wp_enqueue_script( 'radiotax' );
}

Now for the javascript we actually need, create a file in your theme's js folder. We'll call it radiotax.js, and here's the code to put inside:

jQuery(document).ready(function($) {
	var taxonomy = 'mytaxonomy';
	$('#' + taxonomy + 'checklist li :radio, #' + taxonomy + 'checklist-pop :radio').live( 'click', function(){
		var t = $(this), c = t.is(':checked'), id = t.val();
		$('#' + taxonomy + 'checklist li :radio, #' + taxonomy + 'checklist-pop :radio').prop('checked',false);
		$('#in-' + taxonomy + '-' + id + ', #in-popular-' + taxonomy + '-' + id).prop( 'checked', c );
	});
});

So what do these few lines do? Whenever you check a radio button it unchecks all the others (on both tabs) and then checks the radio buttons that correspond to that term.


Conclusion

And with that we are done. WordPress handles all the rest for us. There's room for improvement though... what about adding new terms? I've ommitted that from our metabox, because it's actually incredibly tricky to do. It would involve a lot more javascript and a also a bit of action on the server side.


Update:

As requested by Roberto, here is a link to the code in full on GitHub. It's a class implementation of the code used in this tutorial, so to get started you should only need to change the class' static variables at the top.

Advertisement